extends Control # ============================================================================ # FriendListPanel.gd - 右下角好友列表面板 # ============================================================================ # 独立展示好友在线、好友请求和好友选择。点击好友后通过事件驱动 ChatUI # 切换到对应好友会话,不在面板内承载聊天内容。 # ============================================================================ const PANEL_SIZE: Vector2 = Vector2(384, 492) const PANEL_MARGIN: Vector2 = Vector2(32, 32) const ONLINE_COLOR: Color = Color(0.286, 0.765, 0.486) const OFFLINE_COLOR: Color = Color(0.678, 0.725, 0.773) const TEXT_COLOR: Color = Color(0.188, 0.294, 0.424) const MUTED_COLOR: Color = Color(0.560, 0.639, 0.733) const ACCENT_COLOR: Color = Color(0.258824, 0.627451, 0.913725) const SURFACE_COLOR: Color = Color(1.0, 0.996, 0.984, 0.97) const SURFACE_RAISED_COLOR: Color = Color(1.0, 1.0, 1.0, 0.9) const BORDER_COLOR: Color = Color(0.788, 0.851, 0.910, 0.52) const FRIEND_AVATAR_SIZE: Vector2 = Vector2(48, 48) const FRIEND_AVATAR_CORNER_RADIUS: int = 12 @export var autoRequestFriendList: bool = true var _panel: PanelContainer var _titleLabel: Label var _summaryLabel: Label var _closeButton: Button var _requestList: VBoxContainer var _friendList: VBoxContainer var _statusLabel: Label var _friends: Array[Dictionary] = [] var _requests: Array[Dictionary] = [] var _supported: bool = true var _statusMessage: String = "" var _isOpen: bool = false var _hasRequestedFriendList: bool = false func _ready() -> void: add_to_group("whaletown_escape_dismissible") mouse_filter = Control.MOUSE_FILTER_IGNORE _build_ui() set_panel_open(false) _subscribe_to_events() func _exit_tree() -> void: var eventSystem := _get_event_system() if eventSystem != null: eventSystem.call("disconnect_event", EventNames.CHAT_FRIENDS_UPDATED, _on_friends_updated, self) eventSystem.call("disconnect_event", EventNames.HUD_FRIEND_LIST_TOGGLE, _on_friend_list_toggle, self) eventSystem.call("disconnect_event", EventNames.SETTINGS_CHANGED, _on_settings_changed, self) func set_panel_open(open: bool) -> void: _isOpen = open if is_instance_valid(_panel): _panel.visible = _isOpen if _isOpen and autoRequestFriendList and not _hasRequestedFriendList: _request_initial_friend_list() func is_panel_open() -> bool: return _isOpen func is_escape_dismissible() -> bool: return _isOpen func get_escape_priority() -> int: return 500 func request_escape_close() -> void: set_panel_open(false) func toggle_panel() -> void: set_panel_open(not _isOpen) func _build_ui() -> void: _panel = PanelContainer.new() _panel.name = "FriendListCard" _panel.custom_minimum_size = PANEL_SIZE _panel.mouse_filter = Control.MOUSE_FILTER_STOP _panel.add_theme_stylebox_override("panel", _create_panel_style()) add_child(_panel) _anchor_panel() var margin := MarginContainer.new() margin.add_theme_constant_override("margin_left", 18) margin.add_theme_constant_override("margin_top", 18) margin.add_theme_constant_override("margin_right", 18) margin.add_theme_constant_override("margin_bottom", 18) _panel.add_child(margin) var content := VBoxContainer.new() content.add_theme_constant_override("separation", 12) margin.add_child(content) var header := HBoxContainer.new() header.custom_minimum_size = Vector2(0, 48) header.add_theme_constant_override("separation", 12) content.add_child(header) var titleStack := VBoxContainer.new() titleStack.size_flags_horizontal = Control.SIZE_EXPAND_FILL titleStack.alignment = BoxContainer.ALIGNMENT_CENTER titleStack.add_theme_constant_override("separation", 1) header.add_child(titleStack) _titleLabel = Label.new() _titleLabel.text = "好友" _titleLabel.add_theme_color_override("font_color", TEXT_COLOR) _titleLabel.add_theme_font_size_override("font_size", 22) titleStack.add_child(_titleLabel) var subtitleLabel := Label.new() subtitleLabel.text = "和鲸落镇的伙伴保持联系" subtitleLabel.add_theme_color_override("font_color", MUTED_COLOR) subtitleLabel.add_theme_font_size_override("font_size", 12) titleStack.add_child(subtitleLabel) _closeButton = Button.new() _closeButton.text = "×" _closeButton.tooltip_text = "收起好友列表" _closeButton.custom_minimum_size = Vector2(36, 36) _closeButton.focus_mode = Control.FOCUS_NONE _closeButton.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND _closeButton.add_theme_font_size_override("font_size", 22) _closeButton.add_theme_color_override("font_color", MUTED_COLOR) _closeButton.add_theme_color_override("font_hover_color", ACCENT_COLOR) _closeButton.add_theme_color_override("font_pressed_color", ACCENT_COLOR.darkened(0.12)) _closeButton.add_theme_color_override("font_disabled_color", Color(0.37, 0.48, 0.58, 0.55)) _closeButton.add_theme_stylebox_override("normal", _create_empty_style()) _closeButton.add_theme_stylebox_override("hover", _create_pill_style(Color(0.918, 0.961, 0.992, 0.96), 12)) _closeButton.add_theme_stylebox_override("pressed", _create_pill_style(Color(0.858, 0.925, 0.980, 1.0), 12)) _closeButton.add_theme_stylebox_override("focus", _create_empty_style()) _closeButton.pressed.connect(func() -> void: set_panel_open(false)) header.add_child(_closeButton) var summaryCard := PanelContainer.new() summaryCard.custom_minimum_size = Vector2(0, 54) summaryCard.add_theme_stylebox_override("panel", _create_summary_style()) content.add_child(summaryCard) var summaryRow := HBoxContainer.new() summaryRow.add_theme_constant_override("separation", 10) summaryCard.add_child(summaryRow) var onlineDot := PanelContainer.new() onlineDot.custom_minimum_size = Vector2(10, 10) onlineDot.size_flags_vertical = Control.SIZE_SHRINK_CENTER onlineDot.add_theme_stylebox_override("panel", _create_dot_style(ONLINE_COLOR)) summaryRow.add_child(onlineDot) _summaryLabel = Label.new() _summaryLabel.text = "暂无在线好友" _summaryLabel.size_flags_horizontal = Control.SIZE_EXPAND_FILL _summaryLabel.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _summaryLabel.add_theme_color_override("font_color", Color(0.207843, 0.427451, 0.686275)) _summaryLabel.add_theme_font_size_override("font_size", 15) summaryRow.add_child(_summaryLabel) var chatHint := Label.new() chatHint.text = "选择好友开始私聊" chatHint.vertical_alignment = VERTICAL_ALIGNMENT_CENTER chatHint.add_theme_color_override("font_color", MUTED_COLOR) chatHint.add_theme_font_size_override("font_size", 12) summaryRow.add_child(chatHint) _statusLabel = Label.new() _statusLabel.visible = false _statusLabel.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _statusLabel.add_theme_color_override("font_color", MUTED_COLOR) _statusLabel.add_theme_font_size_override("font_size", 15) content.add_child(_statusLabel) _requestList = VBoxContainer.new() _requestList.add_theme_constant_override("separation", 8) content.add_child(_requestList) var friendsHeading := Label.new() friendsHeading.text = "全部好友" friendsHeading.add_theme_color_override("font_color", MUTED_COLOR) friendsHeading.add_theme_font_size_override("font_size", 13) content.add_child(friendsHeading) var scroll := ScrollContainer.new() scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED scroll.add_theme_stylebox_override("panel", _create_empty_style()) content.add_child(scroll) _friendList = VBoxContainer.new() _friendList.size_flags_horizontal = Control.SIZE_EXPAND_FILL _friendList.add_theme_constant_override("separation", 8) scroll.add_child(_friendList) _render() func _anchor_panel() -> void: _panel.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT) _panel.offset_left = -PANEL_SIZE.x - PANEL_MARGIN.x _panel.offset_top = -PANEL_SIZE.y - PANEL_MARGIN.y _panel.offset_right = -PANEL_MARGIN.x _panel.offset_bottom = -PANEL_MARGIN.y _panel.grow_horizontal = Control.GROW_DIRECTION_BEGIN _panel.grow_vertical = Control.GROW_DIRECTION_BEGIN func _subscribe_to_events() -> void: var eventSystem := _get_event_system() if eventSystem == null: push_warning("FriendListPanel: EventSystem autoload is not available.") return eventSystem.call("connect_event", EventNames.CHAT_FRIENDS_UPDATED, _on_friends_updated, self) eventSystem.call("connect_event", EventNames.HUD_FRIEND_LIST_TOGGLE, _on_friend_list_toggle, self) eventSystem.call("connect_event", EventNames.SETTINGS_CHANGED, _on_settings_changed, self) func _request_initial_friend_list() -> void: _hasRequestedFriendList = true var chatManager := get_node_or_null("/root/ChatManager") if chatManager != null and chatManager.has_method("request_friend_list"): chatManager.call_deferred("request_friend_list") func _on_friend_list_toggle(_data: Variant = null) -> void: toggle_panel() func _on_friends_updated(data: Dictionary) -> void: _friends.clear() _requests.clear() _supported = bool(data.get("supported", true)) _statusMessage = str(data.get("status", "")).strip_edges() var friendsVariant: Variant = data.get("friends", []) if friendsVariant is Array: for friendVariant in friendsVariant: if friendVariant is Dictionary: _friends.append(friendVariant) var requestsVariant: Variant = data.get("requests", []) if requestsVariant is Array: for requestVariant in requestsVariant: if requestVariant is Dictionary: _requests.append(requestVariant) _render() func _on_settings_changed(_data: Dictionary) -> void: _render() func _render() -> void: if not is_instance_valid(_friendList) or not is_instance_valid(_requestList): return _clear_children(_friendList) _clear_children(_requestList) var onlineCount := _count_online_friends() _titleLabel.text = "好友" if is_instance_valid(_summaryLabel): if _friends.is_empty(): _summaryLabel.text = "暂无好友,去广场认识新伙伴吧" else: _summaryLabel.text = "%d 位好友在线,共 %d 位伙伴" % [onlineCount, _friends.size()] if not _supported: _show_status(_statusMessage if not _statusMessage.is_empty() else "当前后端暂不支持好友功能") return _statusLabel.visible = false _render_requests() _render_friends() func _render_requests() -> void: if _requests.is_empty() or not _settings_bool("friend_request_notifications", true): _requestList.visible = false return _requestList.visible = true var titleCard := PanelContainer.new() titleCard.custom_minimum_size = Vector2(0, 34) titleCard.add_theme_stylebox_override("panel", _create_section_style()) _requestList.add_child(titleCard) var title := Label.new() title.text = "待处理请求 %d" % _requests.size() title.vertical_alignment = VERTICAL_ALIGNMENT_CENTER title.add_theme_color_override("font_color", Color(0.207843, 0.427451, 0.686275)) title.add_theme_font_size_override("font_size", 13) titleCard.add_child(title) for request in _requests: _requestList.add_child(_create_request_row(request)) func _render_friends() -> void: if _friends.is_empty(): var emptyCard := PanelContainer.new() emptyCard.custom_minimum_size = Vector2(0, 100) emptyCard.add_theme_stylebox_override("panel", _create_empty_state_style()) _friendList.add_child(emptyCard) var emptyLabel := Label.new() emptyLabel.text = "暂无好友\n靠近玩家按 F 发送好友请求" emptyLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER emptyLabel.vertical_alignment = VERTICAL_ALIGNMENT_CENTER emptyLabel.add_theme_color_override("font_color", MUTED_COLOR) emptyLabel.add_theme_font_size_override("font_size", 14) emptyCard.add_child(emptyLabel) return for friend in _sorted_friends(): _friendList.add_child(_create_friend_row(friend)) func _create_friend_row(friend: Dictionary) -> Control: var userId := _read_user_id(friend) var username := str(friend.get("username", "好友")).strip_edges() if username.is_empty(): username = "好友" var online := bool(friend.get("online", false)) var button := Button.new() button.focus_mode = Control.FOCUS_NONE button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND button.disabled = userId.is_empty() button.custom_minimum_size = Vector2(0, 68) button.text = "" button.add_theme_stylebox_override("normal", _create_friend_row_style(false, online)) button.add_theme_stylebox_override("hover", _create_friend_row_style(true, online)) button.add_theme_stylebox_override("pressed", _create_friend_row_style(true, online, true)) button.add_theme_stylebox_override("disabled", _create_friend_row_style(false, false)) button.add_theme_stylebox_override("focus", _create_empty_style()) button.pressed.connect(func() -> void: _select_friend(userId, username, online)) var row := HBoxContainer.new() row.mouse_filter = Control.MOUSE_FILTER_IGNORE row.set_anchors_preset(Control.PRESET_FULL_RECT) row.offset_left = 10 row.offset_top = 8 row.offset_right = -10 row.offset_bottom = -8 row.add_theme_constant_override("separation", 12) button.add_child(row) row.add_child(_create_avatar(username, online)) var textBox := VBoxContainer.new() textBox.mouse_filter = Control.MOUSE_FILTER_IGNORE textBox.size_flags_horizontal = Control.SIZE_EXPAND_FILL textBox.alignment = BoxContainer.ALIGNMENT_CENTER textBox.add_theme_constant_override("separation", 3) row.add_child(textBox) var nameLabel := Label.new() nameLabel.mouse_filter = Control.MOUSE_FILTER_IGNORE nameLabel.text = username nameLabel.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS nameLabel.add_theme_color_override("font_color", TEXT_COLOR if online else MUTED_COLOR) nameLabel.add_theme_font_size_override("font_size", 16) textBox.add_child(nameLabel) var statusLabel := Label.new() statusLabel.mouse_filter = Control.MOUSE_FILTER_IGNORE statusLabel.text = "正在鲸落镇" if online else "暂时离线" statusLabel.add_theme_color_override("font_color", ONLINE_COLOR if online else MUTED_COLOR) statusLabel.add_theme_font_size_override("font_size", 12) textBox.add_child(statusLabel) var dot := PanelContainer.new() dot.name = "FriendOnlineIndicator" dot.custom_minimum_size = Vector2(8, 8) dot.size_flags_vertical = Control.SIZE_SHRINK_CENTER dot.mouse_filter = Control.MOUSE_FILTER_IGNORE dot.add_theme_stylebox_override("panel", _create_dot_style(ONLINE_COLOR if online else OFFLINE_COLOR)) row.add_child(dot) return button func _create_request_row(request: Dictionary) -> Control: var userId := _read_user_id(request) var username := str(request.get("username", "玩家")).strip_edges() if username.is_empty(): username = "玩家" var rowPanel := PanelContainer.new() rowPanel.custom_minimum_size = Vector2(0, 48) rowPanel.add_theme_stylebox_override("panel", _create_request_style()) var row := HBoxContainer.new() row.add_theme_constant_override("separation", 8) rowPanel.add_child(row) var nameLabel := Label.new() nameLabel.mouse_filter = Control.MOUSE_FILTER_IGNORE nameLabel.text = username nameLabel.size_flags_horizontal = Control.SIZE_EXPAND_FILL nameLabel.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS nameLabel.add_theme_color_override("font_color", TEXT_COLOR) nameLabel.add_theme_font_size_override("font_size", 14) row.add_child(nameLabel) var acceptButton := _create_small_button("接受", Color(0.333, 0.761, 0.514)) acceptButton.pressed.connect(func() -> void: _respond_request(userId, username, true)) row.add_child(acceptButton) var rejectButton := _create_small_button("拒绝", Color(0.812, 0.553, 0.553)) rejectButton.pressed.connect(func() -> void: _respond_request(userId, username, false)) row.add_child(rejectButton) return rowPanel func _select_friend(userId: String, username: String, _online: bool) -> void: if userId.strip_edges().is_empty(): return var eventSystem := _get_event_system() if eventSystem != null: eventSystem.call("emit_event", EventNames.CHAT_FRIEND_SELECTED, { "user_id": userId, "username": username }) func _respond_request(userId: String, username: String, accepted: bool) -> void: if userId.strip_edges().is_empty(): return var chatManager := get_node_or_null("/root/ChatManager") if chatManager == null: return var ok := false if accepted and chatManager.has_method("accept_friend_request"): ok = bool(chatManager.call("accept_friend_request", userId, username)) elif not accepted and chatManager.has_method("reject_friend_request"): ok = bool(chatManager.call("reject_friend_request", userId, username)) if ok: for i in range(_requests.size() - 1, -1, -1): if _read_user_id(_requests[i]) == userId: _requests.remove_at(i) _render() func _show_status(message: String) -> void: _statusLabel.text = message _statusLabel.visible = true _requestList.visible = false func _count_online_friends() -> int: var count := 0 for friend in _friends: if bool(friend.get("online", false)): count += 1 return count func _sorted_friends() -> Array[Dictionary]: var sortedFriends := _friends.duplicate(true) sortedFriends.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: var onlineA := bool(a.get("online", false)) var onlineB := bool(b.get("online", false)) if onlineA != onlineB: return onlineA return str(a.get("username", "")).nocasecmp_to(str(b.get("username", ""))) < 0 ) return sortedFriends func _read_user_id(data: Dictionary) -> String: return str(data.get("user_id", data.get("userId", ""))).strip_edges() func _create_avatar(username: String, online: bool) -> PanelContainer: var avatar := PanelContainer.new() avatar.name = "FriendAvatarPanel" avatar.custom_minimum_size = FRIEND_AVATAR_SIZE avatar.size_flags_horizontal = Control.SIZE_SHRINK_CENTER avatar.size_flags_vertical = Control.SIZE_SHRINK_CENTER avatar.mouse_filter = Control.MOUSE_FILTER_IGNORE avatar.add_theme_stylebox_override("panel", _create_avatar_style(online)) var label := Label.new() label.mouse_filter = Control.MOUSE_FILTER_IGNORE label.text = _avatar_text(username) label.custom_minimum_size = FRIEND_AVATAR_SIZE label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER label.add_theme_color_override("font_color", Color.WHITE) label.add_theme_font_size_override("font_size", 16) avatar.add_child(label) return avatar func _avatar_text(username: String) -> String: var normalized := username.strip_edges() if normalized.is_empty(): return "友" return normalized.substr(0, 1).to_upper() func _create_small_button(text: String, color: Color) -> Button: var button := Button.new() button.text = text button.custom_minimum_size = Vector2(52, 30) button.focus_mode = Control.FOCUS_NONE button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND button.add_theme_font_size_override("font_size", 13) button.add_theme_color_override("font_color", Color.WHITE) button.add_theme_stylebox_override("normal", _create_pill_style(color, 9)) button.add_theme_stylebox_override("hover", _create_pill_style(color.lightened(0.08), 9)) button.add_theme_stylebox_override("pressed", _create_pill_style(color.darkened(0.08), 9)) button.add_theme_stylebox_override("focus", _create_empty_style()) return button func _create_panel_style() -> StyleBoxFlat: var style := StyleBoxFlat.new() style.bg_color = SURFACE_COLOR style.border_width_left = 1 style.border_width_top = 1 style.border_width_right = 1 style.border_width_bottom = 1 style.border_color = BORDER_COLOR style.corner_radius_top_left = 20 style.corner_radius_top_right = 20 style.corner_radius_bottom_left = 20 style.corner_radius_bottom_right = 20 style.shadow_color = Color(0.12549, 0.282353, 0.407843, 0.18) style.shadow_size = 18 style.shadow_offset = Vector2(0, 7) return style func _create_avatar_style(online: bool) -> StyleBoxFlat: var style := StyleBoxFlat.new() style.bg_color = Color(0.361, 0.690, 0.914) if online else Color(0.678, 0.725, 0.773) style.corner_radius_top_left = FRIEND_AVATAR_CORNER_RADIUS style.corner_radius_top_right = FRIEND_AVATAR_CORNER_RADIUS style.corner_radius_bottom_left = FRIEND_AVATAR_CORNER_RADIUS style.corner_radius_bottom_right = FRIEND_AVATAR_CORNER_RADIUS style.border_width_left = 1 style.border_width_top = 1 style.border_width_right = 1 style.border_width_bottom = 1 style.border_color = Color(1, 1, 1, 0.82) return style func _create_summary_style() -> StyleBoxFlat: var style := StyleBoxFlat.new() style.bg_color = Color(0.918, 0.961, 0.992, 0.96) style.border_width_left = 1 style.border_width_top = 1 style.border_width_right = 1 style.border_width_bottom = 1 style.border_color = Color(0.76, 0.86, 0.94, 0.62) style.corner_radius_top_left = 12 style.corner_radius_top_right = 12 style.corner_radius_bottom_left = 12 style.corner_radius_bottom_right = 12 style.content_margin_left = 14 style.content_margin_right = 14 return style func _create_dot_style(color: Color) -> StyleBoxFlat: var style := StyleBoxFlat.new() style.bg_color = color style.corner_radius_top_left = 5 style.corner_radius_top_right = 5 style.corner_radius_bottom_left = 5 style.corner_radius_bottom_right = 5 return style func _create_section_style() -> StyleBoxFlat: var style := StyleBoxFlat.new() style.bg_color = Color(0.948, 0.974, 0.996, 0.96) style.border_width_left = 1 style.border_width_top = 1 style.border_width_right = 1 style.border_width_bottom = 1 style.border_color = Color(0.78, 0.87, 0.94, 0.54) style.corner_radius_top_left = 9 style.corner_radius_top_right = 9 style.corner_radius_bottom_left = 9 style.corner_radius_bottom_right = 9 style.content_margin_left = 12 style.content_margin_right = 12 return style func _create_empty_state_style() -> StyleBoxFlat: var style := StyleBoxFlat.new() style.bg_color = Color(0.965, 0.982, 0.996, 0.86) style.border_width_left = 1 style.border_width_top = 1 style.border_width_right = 1 style.border_width_bottom = 1 style.border_color = Color(0.80, 0.88, 0.94, 0.52) style.corner_radius_top_left = 12 style.corner_radius_top_right = 12 style.corner_radius_bottom_left = 12 style.corner_radius_bottom_right = 12 return style func _create_request_style() -> StyleBoxFlat: var style := StyleBoxFlat.new() style.bg_color = Color(1, 1, 1, 0.94) style.border_width_left = 1 style.border_width_top = 1 style.border_width_right = 1 style.border_width_bottom = 1 style.border_color = Color(0.78, 0.86, 0.93, 0.64) style.corner_radius_top_left = 10 style.corner_radius_top_right = 10 style.corner_radius_bottom_left = 10 style.corner_radius_bottom_right = 10 style.content_margin_left = 10 style.content_margin_top = 8 style.content_margin_right = 10 style.content_margin_bottom = 8 return style func _create_friend_row_style(isHovered: bool, isOnline: bool, isPressed: bool = false) -> StyleBoxFlat: var style := StyleBoxFlat.new() style.bg_color = SURFACE_RAISED_COLOR if isOnline else Color(0.965, 0.973, 0.982, 0.86) if isHovered: style.bg_color = Color(0.918, 0.961, 0.992, 0.98) if isPressed: style.bg_color = style.bg_color.darkened(0.12) style.border_width_left = 1 style.border_width_top = 1 style.border_width_right = 1 style.border_width_bottom = 1 style.border_color = Color(0.55, 0.78, 0.95, 0.72) if isHovered else Color(0.80, 0.87, 0.93, 0.56) style.corner_radius_top_left = 12 style.corner_radius_top_right = 12 style.corner_radius_bottom_left = 12 style.corner_radius_bottom_right = 12 return style func _create_pill_style(color: Color, radius: int) -> StyleBoxFlat: var style := StyleBoxFlat.new() style.bg_color = color style.corner_radius_top_left = radius style.corner_radius_top_right = radius style.corner_radius_bottom_left = radius style.corner_radius_bottom_right = radius style.content_margin_left = 10 style.content_margin_right = 10 style.content_margin_top = 5 style.content_margin_bottom = 5 return style func _create_empty_style() -> StyleBoxEmpty: return StyleBoxEmpty.new() func _clear_children(container: Node) -> void: for child in container.get_children(): container.remove_child(child) child.queue_free() func _get_event_system() -> Node: return get_node_or_null("/root/EventSystem") func _settings_bool(key: String, defaultValue: bool) -> bool: var settingsManager := get_node_or_null("/root/SettingsManager") if settingsManager != null and settingsManager.has_method("get_bool"): return bool(settingsManager.call("get_bool", key)) return defaultValue