ZZ revised this gist . Go to revision
1 file changed, 143 insertions, 146 deletions
clipboard_ui.gd
| @@ -3,157 +3,154 @@ class_name ClipboardUIHelper | |||
| 3 | 3 | ||
| 4 | 4 | # Builds the Network tab content into a provided container page | |
| 5 | 5 | static func build_network_ui(page_container: Node, engine: Node) -> void: | |
| 6 | - | ## 1. CLEANUP PREVIOUS RENDERS: Safely look for and wipe old structural layout node | |
| 7 | - | var old_wrapper = page_container.get_node_or_null("NetworkMarginWrapper") | |
| 8 | - | if old_wrapper: | |
| 9 | - | old_wrapper.queue_free() | |
| 10 | - | page_container.remove_child(old_wrapper) | |
| 11 | - | ||
| 12 | - | ## 2. MAIN PAGE LAYOUT: The root wrapper for this specific tab page | |
| 13 | - | var margin_wrapper = MarginContainer.new() | |
| 14 | - | margin_wrapper.name = "NetworkMarginWrapper" | |
| 15 | - | margin_wrapper.add_theme_constant_override("margin_left", 50) | |
| 16 | - | margin_wrapper.add_theme_constant_override("margin_top", 40) | |
| 17 | - | page_container.add_child(margin_wrapper) | |
| 18 | - | ||
| 19 | - | var main_stack = VBoxContainer.new() | |
| 20 | - | main_stack.add_theme_constant_override("separation", 15) | |
| 21 | - | margin_wrapper.add_child(main_stack) | |
| 22 | - | ||
| 23 | - | # ---------------------------------------------------- | |
| 24 | - | # ROW 1: Connection Actions (Start Server / Connect Client) | |
| 25 | - | # ---------------------------------------------------- | |
| 26 | - | var row_actions = HBoxContainer.new() | |
| 27 | - | row_actions.add_theme_constant_override("separation", 15) | |
| 28 | - | main_stack.add_child(row_actions) | |
| 29 | - | ||
| 30 | - | var btn_server = Button.new() | |
| 31 | - | btn_server.text = "Start Server" | |
| 32 | - | btn_server.pressed.connect(func(): _on_start_server_pressed(engine)) | |
| 33 | - | row_actions.add_child(btn_server) | |
| 34 | - | ||
| 35 | - | var btn_client = Button.new() | |
| 36 | - | btn_client.text = "Connect Client" | |
| 37 | - | btn_client.pressed.connect(func(): _on_start_client_pressed(engine)) | |
| 38 | - | row_actions.add_child(btn_client) | |
| 39 | - | ||
| 40 | - | # ---------------------------------------------------- | |
| 41 | - | # ROW 2: Address Info (IP Input & Port Input) | |
| 42 | - | # ---------------------------------------------------- | |
| 43 | - | var row_address = HBoxContainer.new() | |
| 44 | - | row_address.add_theme_constant_override("separation", 15) | |
| 45 | - | main_stack.add_child(row_address) | |
| 46 | - | ||
| 47 | - | var ip_input = LineEdit.new() | |
| 48 | - | ip_input.text = engine.server_ip | |
| 49 | - | ip_input.placeholder_text = "Target Server IP" | |
| 50 | - | ip_input.custom_minimum_size = Vector2(180, 35) | |
| 51 | - | row_address.add_child(ip_input) | |
| 52 | - | ||
| 53 | - | var port_input = LineEdit.new() | |
| 54 | - | port_input.text = str(engine.port_number) | |
| 55 | - | port_input.placeholder_text = "Port" | |
| 56 | - | port_input.custom_minimum_size = Vector2(100, 35) | |
| 57 | - | row_address.add_child(port_input) | |
| 58 | - | ||
| 59 | - | # ---------------------------------------------------- | |
| 60 | - | # ROW 3: Security & Utility (Passphrase, Generate, Copy) | |
| 61 | - | # ---------------------------------------------------- | |
| 62 | - | var row_security = HBoxContainer.new() | |
| 63 | - | row_security.add_theme_constant_override("separation", 15) | |
| 64 | - | main_stack.add_child(row_security) | |
| 65 | - | ||
| 66 | - | var key_input = LineEdit.new() | |
| 67 | - | key_input.text = engine.secret_key | |
| 68 | - | key_input.secret = true | |
| 69 | - | key_input.placeholder_text = "Shared Secret Key Passphrase" | |
| 70 | - | key_input.custom_minimum_size = Vector2(300, 35) | |
| 71 | - | row_security.add_child(key_input) | |
| 72 | - | ||
| 73 | - | var btn_generate = Button.new() | |
| 74 | - | btn_generate.text = "New Key" | |
| 75 | - | btn_generate.custom_minimum_size = Vector2(0, 35) | |
| 76 | - | btn_generate.pressed.connect(func(): _on_generate_key(engine, key_input)) | |
| 77 | - | row_security.add_child(btn_generate) | |
| 78 | - | ||
| 79 | - | var btn_copy = Button.new() | |
| 80 | - | btn_copy.text = "Copy Key" | |
| 81 | - | btn_copy.custom_minimum_size = Vector2(0, 35) | |
| 82 | - | btn_copy.pressed.connect(func(): engine.copy_key_to_clipboard()) | |
| 83 | - | row_security.add_child(btn_copy) | |
| 84 | - | ||
| 85 | - | # ---------------------------------------------------- | |
| 86 | - | # ROW 4: Manual Sync Input | |
| 87 | - | # ---------------------------------------------------- | |
| 88 | - | var row_input = HBoxContainer.new() | |
| 89 | - | row_address.add_theme_constant_override("separation", 15) | |
| 90 | - | main_stack.add_child(row_input) | |
| 91 | - | ||
| 92 | - | var line_edit = LineEdit.new() | |
| 93 | - | line_edit.custom_minimum_size = Vector2(400, 40) | |
| 94 | - | line_edit.placeholder_text = "Type and press Enter to securely sync..." | |
| 95 | - | # Wire the UI input directly to the engine's RPC method using encryption | |
| 96 | - | line_edit.text_submitted.connect(func(new_text): engine.receive_secure_clipboard(engine.encrypt_string(new_text))) | |
| 97 | - | row_input.add_child(line_edit) | |
| 98 | - | ||
| 99 | - | # ---------------------------------------------------- | |
| 100 | - | # ROW 5: Status Panel (Replaces draw_rect entirely) | |
| 101 | - | # ---------------------------------------------------- | |
| 102 | - | var row_status = HBoxContainer.new() | |
| 103 | - | row_status.add_theme_constant_override("separation", 15) | |
| 104 | - | main_stack.add_child(row_status) | |
| 105 | - | ||
| 106 | - | var status_card = PanelContainer.new() | |
| 107 | - | status_card.custom_minimum_size = Vector2(400, 80) | |
| 108 | - | status_card.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN | |
| 109 | - | ||
| 110 | - | var card_padding = MarginContainer.new() | |
| 111 | - | card_padding.add_theme_constant_override("margin_left", 30) | |
| 112 | - | card_padding.add_theme_constant_override("margin_right", 30) | |
| 113 | - | card_padding.add_theme_constant_override("margin_top", 15) | |
| 114 | - | card_padding.add_theme_constant_override("margin_bottom", 15) | |
| 115 | - | ||
| 116 | - | status_card.add_child(card_padding) | |
| 117 | - | ||
| 118 | - | var status_label = RichTextLabel.new() | |
| 119 | - | status_label.text = "Loading..." | |
| 120 | - | status_label.selection_enabled = true | |
| 121 | - | status_label.add_theme_font_size_override("font_size", 18) | |
| 122 | - | status_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL | |
| 123 | - | card_padding.add_child(status_label) | |
| 124 | - | ||
| 125 | - | # StyleBoxFlat to mimic your old draw_rect background | |
| 126 | - | var custom_style = StyleBoxFlat.new() | |
| 127 | - | custom_style.bg_color = Color(0.13, 0.13, 0.13, 1.0) | |
| 128 | - | custom_style.set_corner_radius_all(4) | |
| 129 | - | status_card.add_theme_stylebox_override("panel", custom_style) | |
| 130 | - | ||
| 131 | - | main_stack.add_child(status_card) | |
| 132 | - | ||
| 133 | - | # Listen for engine status updates and wire them to the label | |
| 134 | - | engine.status_changed.connect(func(new_status_text): _update_status_label(status_label, new_status_text)) | |
| 135 | - | ||
| 136 | - | # -- Static Helper Functions for UI Wiring -- | |
| 6 | + | ## FIX: Skip building if UI already exists. | |
| 7 | + | ## This preserves state, prevents signal duplication, and stops the "Loading..." reset. | |
| 8 | + | if page_container.has_node("NetworkMarginWrapper"): | |
| 9 | + | return | |
| 10 | + | ||
| 11 | + | # ---------------------------------------------------- | |
| 12 | + | # MAIN PAGE LAYOUT: The root wrapper for this specific tab page | |
| 13 | + | # ---------------------------------------------------- | |
| 14 | + | var margin_wrapper = MarginContainer.new() | |
| 15 | + | margin_wrapper.name = "NetworkMarginWrapper" | |
| 16 | + | margin_wrapper.add_theme_constant_override("margin_left", 50) | |
| 17 | + | margin_wrapper.add_theme_constant_override("margin_top", 40) | |
| 18 | + | page_container.add_child(margin_wrapper) | |
| 19 | + | ||
| 20 | + | var main_stack = VBoxContainer.new() | |
| 21 | + | main_stack.add_theme_constant_override("separation", 15) | |
| 22 | + | margin_wrapper.add_child(main_stack) | |
| 23 | + | ||
| 24 | + | # ---------------------------------------------------- | |
| 25 | + | # ROW 1: Connection Actions | |
| 26 | + | # ---------------------------------------------------- | |
| 27 | + | var row_actions = HBoxContainer.new() | |
| 28 | + | row_actions.add_theme_constant_override("separation", 15) | |
| 29 | + | main_stack.add_child(row_actions) | |
| 30 | + | ||
| 31 | + | var btn_server = Button.new() | |
| 32 | + | btn_server.text = "Start Server" | |
| 33 | + | btn_server.pressed.connect(func(): _on_start_server_pressed(engine)) | |
| 34 | + | row_actions.add_child(btn_server) | |
| 35 | + | ||
| 36 | + | var btn_client = Button.new() | |
| 37 | + | btn_client.text = "Connect Client" | |
| 38 | + | btn_client.pressed.connect(func(): _on_start_client_pressed(engine)) | |
| 39 | + | row_actions.add_child(btn_client) | |
| 40 | + | ||
| 41 | + | # ---------------------------------------------------- | |
| 42 | + | # ROW 2: Address Info | |
| 43 | + | # ---------------------------------------------------- | |
| 44 | + | var row_address = HBoxContainer.new() | |
| 45 | + | row_address.add_theme_constant_override("separation", 15) | |
| 46 | + | main_stack.add_child(row_address) | |
| 47 | + | ||
| 48 | + | var ip_input = LineEdit.new() | |
| 49 | + | ip_input.text = str(engine.server_ip) | |
| 50 | + | ip_input.placeholder_text = "Target Server IP" | |
| 51 | + | ip_input.custom_minimum_size = Vector2(180, 35) | |
| 52 | + | row_address.add_child(ip_input) | |
| 53 | + | ||
| 54 | + | var port_input = LineEdit.new() | |
| 55 | + | port_input.text = str(engine.port_number) | |
| 56 | + | port_input.placeholder_text = "Port" | |
| 57 | + | port_input.custom_minimum_size = Vector2(100, 35) | |
| 58 | + | row_address.add_child(port_input) | |
| 59 | + | ||
| 60 | + | # ---------------------------------------------------- | |
| 61 | + | # ROW 3: Security & Utility | |
| 62 | + | # ---------------------------------------------------- | |
| 63 | + | var row_security = HBoxContainer.new() | |
| 64 | + | row_security.add_theme_constant_override("separation", 15) | |
| 65 | + | main_stack.add_child(row_security) | |
| 66 | + | ||
| 67 | + | var key_input = LineEdit.new() | |
| 68 | + | key_input.text = engine.secret_key | |
| 69 | + | key_input.secret = true | |
| 70 | + | key_input.placeholder_text = "Shared Secret Key Passphrase" | |
| 71 | + | key_input.custom_minimum_size = Vector2(300, 35) | |
| 72 | + | row_security.add_child(key_input) | |
| 73 | + | ||
| 74 | + | var btn_generate = Button.new() | |
| 75 | + | btn_generate.text = "New Key" | |
| 76 | + | btn_generate.custom_minimum_size = Vector2(0, 35) | |
| 77 | + | btn_generate.pressed.connect(func(): _on_generate_key(engine, key_input)) | |
| 78 | + | row_security.add_child(btn_generate) | |
| 79 | + | ||
| 80 | + | var btn_copy = Button.new() | |
| 81 | + | btn_copy.text = "Copy Key" | |
| 82 | + | btn_copy.custom_minimum_size = Vector2(0, 35) | |
| 83 | + | btn_copy.pressed.connect(func(): engine.copy_key_to_clipboard()) | |
| 84 | + | row_security.add_child(btn_copy) | |
| 85 | + | ||
| 86 | + | # ---------------------------------------------------- | |
| 87 | + | # ROW 4: Manual Sync Input | |
| 88 | + | # ---------------------------------------------------- | |
| 89 | + | var row_input = HBoxContainer.new() | |
| 90 | + | row_address.add_theme_constant_override("separation", 15) | |
| 91 | + | main_stack.add_child(row_input) | |
| 92 | + | ||
| 93 | + | var line_edit = LineEdit.new() | |
| 94 | + | line_edit.custom_minimum_size = Vector2(400, 40) | |
| 95 | + | line_edit.placeholder_text = "Type and press Enter to securely sync..." | |
| 96 | + | line_edit.text_submitted.connect(func(new_text): engine.receive_secure_clipboard(engine.encrypt_string(new_text))) | |
| 97 | + | row_input.add_child(line_edit) | |
| 98 | + | ||
| 99 | + | # ---------------------------------------------------- | |
| 100 | + | # ROW 5: Status Panel | |
| 101 | + | # ---------------------------------------------------- | |
| 102 | + | var row_status = HBoxContainer.new() | |
| 103 | + | row_status.add_theme_constant_override("separation", 15) | |
| 104 | + | main_stack.add_child(row_status) | |
| 105 | + | ||
| 106 | + | var status_card = PanelContainer.new() | |
| 107 | + | status_card.custom_minimum_size = Vector2(400, 80) | |
| 108 | + | status_card.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN | |
| 109 | + | ||
| 110 | + | var card_padding = MarginContainer.new() | |
| 111 | + | card_padding.add_theme_constant_override("margin_left", 30) | |
| 112 | + | card_padding.add_theme_constant_override("margin_right", 30) | |
| 113 | + | card_padding.add_theme_constant_override("margin_top", 15) | |
| 114 | + | card_padding.add_theme_constant_override("margin_bottom", 15) | |
| 115 | + | ||
| 116 | + | status_card.add_child(card_padding) | |
| 117 | + | ||
| 118 | + | var status_label = RichTextLabel.new() | |
| 119 | + | status_label.text = "Initializing..." # Temporary until first signal fires | |
| 120 | + | status_label.selection_enabled = true | |
| 121 | + | status_label.add_theme_font_size_override("font_size", 18) | |
| 122 | + | status_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL | |
| 123 | + | card_padding.add_child(status_label) | |
| 124 | + | ||
| 125 | + | var custom_style = StyleBoxFlat.new() | |
| 126 | + | custom_style.bg_color = Color(0.13, 0.13, 0.13, 1.0) | |
| 127 | + | custom_style.set_corner_radius_all(4) | |
| 128 | + | status_card.add_theme_stylebox_override("panel", custom_style) | |
| 129 | + | ||
| 130 | + | main_stack.add_child(status_card) | |
| 131 | + | ||
| 132 | + | # 🔗 Connect signal exactly ONCE when UI is built | |
| 133 | + | engine.status_changed.connect(func(new_status_text): _update_status_label(status_label, new_status_text)) | |
| 134 | + | ||
| 135 | + | # -- Static Helper Functions -- | |
| 137 | 136 | ||
| 138 | 137 | static func _on_start_server_pressed(engine: Node) -> void: | |
| 139 | - | # Reads current properties. In a real app you might pass inputs as arguments instead. | |
| 140 | - | engine.start_server(int(engine.port_number)) | |
| 138 | + | engine.start_server(int(engine.port_number)) | |
| 141 | 139 | ||
| 142 | 140 | static func _on_start_client_pressed(engine: Node) -> void: | |
| 143 | - | engine.start_client(str(engine.server_ip), int(engine.port_number)) | |
| 141 | + | engine.start_client(str(engine.server_ip), int(engine.port_number)) | |
| 144 | 142 | ||
| 145 | 143 | static func _on_generate_key(engine: Node, key_input: LineEdit) -> void: | |
| 146 | - | var new_key = engine.generate_random_key(16) | |
| 147 | - | engine.secret_key = new_key | |
| 148 | - | key_input.text = new_key | |
| 144 | + | var new_key = engine.generate_random_key(16) | |
| 145 | + | engine.secret_key = new_key | |
| 146 | + | key_input.text = new_key | |
| 149 | 147 | ||
| 150 | 148 | static func _update_status_label(label: RichTextLabel, text: String) -> void: | |
| 151 | - | label.text = text | |
| 152 | - | # Calculate dynamic color state based on connection status | |
| 153 | - | var target_color: Color = Color.GREEN | |
| 154 | - | if "Disconnected" in text or "Offline" in text: | |
| 155 | - | target_color = Color.YELLOW | |
| 156 | - | elif "Reconnecting" in text: | |
| 157 | - | target_color = Color.ORANGE | |
| 158 | - | ||
| 159 | - | label.add_theme_color_override("default_color", target_color) | |
| 149 | + | label.text = text | |
| 150 | + | var target_color: Color = Color.GREEN | |
| 151 | + | if "Disconnected" in text or "Offline" in text: | |
| 152 | + | target_color = Color.YELLOW | |
| 153 | + | elif "Reconnecting" in text: | |
| 154 | + | target_color = Color.ORANGE | |
| 155 | + | ||
| 156 | + | label.add_theme_color_override("default_color", target_color) | |
ZZ revised this gist . Go to revision
1 file changed, 159 insertions
clipboard_ui.gd(file created)
| @@ -0,0 +1,159 @@ | |||
| 1 | + | extends Node | |
| 2 | + | class_name ClipboardUIHelper | |
| 3 | + | ||
| 4 | + | # Builds the Network tab content into a provided container page | |
| 5 | + | static func build_network_ui(page_container: Node, engine: Node) -> void: | |
| 6 | + | ## 1. CLEANUP PREVIOUS RENDERS: Safely look for and wipe old structural layout node | |
| 7 | + | var old_wrapper = page_container.get_node_or_null("NetworkMarginWrapper") | |
| 8 | + | if old_wrapper: | |
| 9 | + | old_wrapper.queue_free() | |
| 10 | + | page_container.remove_child(old_wrapper) | |
| 11 | + | ||
| 12 | + | ## 2. MAIN PAGE LAYOUT: The root wrapper for this specific tab page | |
| 13 | + | var margin_wrapper = MarginContainer.new() | |
| 14 | + | margin_wrapper.name = "NetworkMarginWrapper" | |
| 15 | + | margin_wrapper.add_theme_constant_override("margin_left", 50) | |
| 16 | + | margin_wrapper.add_theme_constant_override("margin_top", 40) | |
| 17 | + | page_container.add_child(margin_wrapper) | |
| 18 | + | ||
| 19 | + | var main_stack = VBoxContainer.new() | |
| 20 | + | main_stack.add_theme_constant_override("separation", 15) | |
| 21 | + | margin_wrapper.add_child(main_stack) | |
| 22 | + | ||
| 23 | + | # ---------------------------------------------------- | |
| 24 | + | # ROW 1: Connection Actions (Start Server / Connect Client) | |
| 25 | + | # ---------------------------------------------------- | |
| 26 | + | var row_actions = HBoxContainer.new() | |
| 27 | + | row_actions.add_theme_constant_override("separation", 15) | |
| 28 | + | main_stack.add_child(row_actions) | |
| 29 | + | ||
| 30 | + | var btn_server = Button.new() | |
| 31 | + | btn_server.text = "Start Server" | |
| 32 | + | btn_server.pressed.connect(func(): _on_start_server_pressed(engine)) | |
| 33 | + | row_actions.add_child(btn_server) | |
| 34 | + | ||
| 35 | + | var btn_client = Button.new() | |
| 36 | + | btn_client.text = "Connect Client" | |
| 37 | + | btn_client.pressed.connect(func(): _on_start_client_pressed(engine)) | |
| 38 | + | row_actions.add_child(btn_client) | |
| 39 | + | ||
| 40 | + | # ---------------------------------------------------- | |
| 41 | + | # ROW 2: Address Info (IP Input & Port Input) | |
| 42 | + | # ---------------------------------------------------- | |
| 43 | + | var row_address = HBoxContainer.new() | |
| 44 | + | row_address.add_theme_constant_override("separation", 15) | |
| 45 | + | main_stack.add_child(row_address) | |
| 46 | + | ||
| 47 | + | var ip_input = LineEdit.new() | |
| 48 | + | ip_input.text = engine.server_ip | |
| 49 | + | ip_input.placeholder_text = "Target Server IP" | |
| 50 | + | ip_input.custom_minimum_size = Vector2(180, 35) | |
| 51 | + | row_address.add_child(ip_input) | |
| 52 | + | ||
| 53 | + | var port_input = LineEdit.new() | |
| 54 | + | port_input.text = str(engine.port_number) | |
| 55 | + | port_input.placeholder_text = "Port" | |
| 56 | + | port_input.custom_minimum_size = Vector2(100, 35) | |
| 57 | + | row_address.add_child(port_input) | |
| 58 | + | ||
| 59 | + | # ---------------------------------------------------- | |
| 60 | + | # ROW 3: Security & Utility (Passphrase, Generate, Copy) | |
| 61 | + | # ---------------------------------------------------- | |
| 62 | + | var row_security = HBoxContainer.new() | |
| 63 | + | row_security.add_theme_constant_override("separation", 15) | |
| 64 | + | main_stack.add_child(row_security) | |
| 65 | + | ||
| 66 | + | var key_input = LineEdit.new() | |
| 67 | + | key_input.text = engine.secret_key | |
| 68 | + | key_input.secret = true | |
| 69 | + | key_input.placeholder_text = "Shared Secret Key Passphrase" | |
| 70 | + | key_input.custom_minimum_size = Vector2(300, 35) | |
| 71 | + | row_security.add_child(key_input) | |
| 72 | + | ||
| 73 | + | var btn_generate = Button.new() | |
| 74 | + | btn_generate.text = "New Key" | |
| 75 | + | btn_generate.custom_minimum_size = Vector2(0, 35) | |
| 76 | + | btn_generate.pressed.connect(func(): _on_generate_key(engine, key_input)) | |
| 77 | + | row_security.add_child(btn_generate) | |
| 78 | + | ||
| 79 | + | var btn_copy = Button.new() | |
| 80 | + | btn_copy.text = "Copy Key" | |
| 81 | + | btn_copy.custom_minimum_size = Vector2(0, 35) | |
| 82 | + | btn_copy.pressed.connect(func(): engine.copy_key_to_clipboard()) | |
| 83 | + | row_security.add_child(btn_copy) | |
| 84 | + | ||
| 85 | + | # ---------------------------------------------------- | |
| 86 | + | # ROW 4: Manual Sync Input | |
| 87 | + | # ---------------------------------------------------- | |
| 88 | + | var row_input = HBoxContainer.new() | |
| 89 | + | row_address.add_theme_constant_override("separation", 15) | |
| 90 | + | main_stack.add_child(row_input) | |
| 91 | + | ||
| 92 | + | var line_edit = LineEdit.new() | |
| 93 | + | line_edit.custom_minimum_size = Vector2(400, 40) | |
| 94 | + | line_edit.placeholder_text = "Type and press Enter to securely sync..." | |
| 95 | + | # Wire the UI input directly to the engine's RPC method using encryption | |
| 96 | + | line_edit.text_submitted.connect(func(new_text): engine.receive_secure_clipboard(engine.encrypt_string(new_text))) | |
| 97 | + | row_input.add_child(line_edit) | |
| 98 | + | ||
| 99 | + | # ---------------------------------------------------- | |
| 100 | + | # ROW 5: Status Panel (Replaces draw_rect entirely) | |
| 101 | + | # ---------------------------------------------------- | |
| 102 | + | var row_status = HBoxContainer.new() | |
| 103 | + | row_status.add_theme_constant_override("separation", 15) | |
| 104 | + | main_stack.add_child(row_status) | |
| 105 | + | ||
| 106 | + | var status_card = PanelContainer.new() | |
| 107 | + | status_card.custom_minimum_size = Vector2(400, 80) | |
| 108 | + | status_card.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN | |
| 109 | + | ||
| 110 | + | var card_padding = MarginContainer.new() | |
| 111 | + | card_padding.add_theme_constant_override("margin_left", 30) | |
| 112 | + | card_padding.add_theme_constant_override("margin_right", 30) | |
| 113 | + | card_padding.add_theme_constant_override("margin_top", 15) | |
| 114 | + | card_padding.add_theme_constant_override("margin_bottom", 15) | |
| 115 | + | ||
| 116 | + | status_card.add_child(card_padding) | |
| 117 | + | ||
| 118 | + | var status_label = RichTextLabel.new() | |
| 119 | + | status_label.text = "Loading..." | |
| 120 | + | status_label.selection_enabled = true | |
| 121 | + | status_label.add_theme_font_size_override("font_size", 18) | |
| 122 | + | status_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL | |
| 123 | + | card_padding.add_child(status_label) | |
| 124 | + | ||
| 125 | + | # StyleBoxFlat to mimic your old draw_rect background | |
| 126 | + | var custom_style = StyleBoxFlat.new() | |
| 127 | + | custom_style.bg_color = Color(0.13, 0.13, 0.13, 1.0) | |
| 128 | + | custom_style.set_corner_radius_all(4) | |
| 129 | + | status_card.add_theme_stylebox_override("panel", custom_style) | |
| 130 | + | ||
| 131 | + | main_stack.add_child(status_card) | |
| 132 | + | ||
| 133 | + | # Listen for engine status updates and wire them to the label | |
| 134 | + | engine.status_changed.connect(func(new_status_text): _update_status_label(status_label, new_status_text)) | |
| 135 | + | ||
| 136 | + | # -- Static Helper Functions for UI Wiring -- | |
| 137 | + | ||
| 138 | + | static func _on_start_server_pressed(engine: Node) -> void: | |
| 139 | + | # Reads current properties. In a real app you might pass inputs as arguments instead. | |
| 140 | + | engine.start_server(int(engine.port_number)) | |
| 141 | + | ||
| 142 | + | static func _on_start_client_pressed(engine: Node) -> void: | |
| 143 | + | engine.start_client(str(engine.server_ip), int(engine.port_number)) | |
| 144 | + | ||
| 145 | + | static func _on_generate_key(engine: Node, key_input: LineEdit) -> void: | |
| 146 | + | var new_key = engine.generate_random_key(16) | |
| 147 | + | engine.secret_key = new_key | |
| 148 | + | key_input.text = new_key | |
| 149 | + | ||
| 150 | + | static func _update_status_label(label: RichTextLabel, text: String) -> void: | |
| 151 | + | label.text = text | |
| 152 | + | # Calculate dynamic color state based on connection status | |
| 153 | + | var target_color: Color = Color.GREEN | |
| 154 | + | if "Disconnected" in text or "Offline" in text: | |
| 155 | + | target_color = Color.YELLOW | |
| 156 | + | elif "Reconnecting" in text: | |
| 157 | + | target_color = Color.ORANGE | |
| 158 | + | ||
| 159 | + | label.add_theme_color_override("default_color", target_color) | |