class_name LoginScreen extends CanvasLayer ## LoginScreen - Email/password login form for online play signal login_successful(user_data: Dictionary) signal register_requested signal forgot_password_requested signal back_pressed const WINDOW_SIZE := Vector2(400, 500) # UI Components var background: PanelContainer var main_vbox: VBoxContainer var title_label: Label var email_input: LineEdit var password_input: LineEdit var login_button: Button var register_link: Button var forgot_password_link: Button var back_button: Button var error_label: Label var loading_spinner: Control var status_label: Label # State var _is_loading: bool = false func _ready() -> void: layer = 100 _create_ui() # Connect to NetworkManager signals if NetworkManager: NetworkManager.authenticated.connect(_on_authenticated) NetworkManager.authentication_failed.connect(_on_auth_failed) func _create_ui() -> void: # Background panel background = PanelContainer.new() add_child(background) background.position = Vector2.ZERO background.size = WINDOW_SIZE background.add_theme_stylebox_override("panel", _create_panel_style()) # Main layout with margin var margin = MarginContainer.new() background.add_child(margin) margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) margin.add_theme_constant_override("margin_left", 40) margin.add_theme_constant_override("margin_right", 40) margin.add_theme_constant_override("margin_top", 30) margin.add_theme_constant_override("margin_bottom", 30) main_vbox = VBoxContainer.new() margin.add_child(main_vbox) main_vbox.add_theme_constant_override("separation", 15) # Title _create_title() # Login form _create_form() # Error label _create_error_label() # Spacer var spacer = Control.new() spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL main_vbox.add_child(spacer) # Links _create_links() # Back button _create_back_button() func _create_title() -> void: title_label = Label.new() title_label.text = "LOGIN" title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title_label.add_theme_font_size_override("font_size", 32) title_label.add_theme_color_override("font_color", Color(1.0, 0.95, 0.8)) main_vbox.add_child(title_label) # Separator var separator = HSeparator.new() separator.add_theme_stylebox_override("separator", _create_separator_style()) main_vbox.add_child(separator) # Spacer var spacer = Control.new() spacer.custom_minimum_size.y = 20 main_vbox.add_child(spacer) func _create_form() -> void: # Email field var email_label = Label.new() email_label.text = "Email" email_label.add_theme_font_size_override("font_size", 16) email_label.add_theme_color_override("font_color", Color(0.9, 0.85, 0.7)) main_vbox.add_child(email_label) email_input = LineEdit.new() email_input.placeholder_text = "Enter your email" email_input.custom_minimum_size = Vector2(0, 40) _style_input(email_input) email_input.text_submitted.connect(_on_input_submitted) main_vbox.add_child(email_input) # Password field var password_label = Label.new() password_label.text = "Password" password_label.add_theme_font_size_override("font_size", 16) password_label.add_theme_color_override("font_color", Color(0.9, 0.85, 0.7)) main_vbox.add_child(password_label) password_input = LineEdit.new() password_input.placeholder_text = "Enter your password" password_input.secret = true password_input.custom_minimum_size = Vector2(0, 40) _style_input(password_input) password_input.text_submitted.connect(_on_input_submitted) main_vbox.add_child(password_input) # Login button var button_spacer = Control.new() button_spacer.custom_minimum_size.y = 10 main_vbox.add_child(button_spacer) login_button = Button.new() login_button.text = "Login" login_button.custom_minimum_size = Vector2(0, 45) _style_button(login_button, true) login_button.pressed.connect(_on_login_pressed) main_vbox.add_child(login_button) func _create_error_label() -> void: error_label = Label.new() error_label.text = "" error_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER error_label.add_theme_font_size_override("font_size", 14) error_label.add_theme_color_override("font_color", Color(1.0, 0.4, 0.4)) error_label.autowrap_mode = TextServer.AUTOWRAP_WORD error_label.visible = false main_vbox.add_child(error_label) func _create_links() -> void: var links_container = VBoxContainer.new() links_container.add_theme_constant_override("separation", 8) main_vbox.add_child(links_container) # Register link register_link = Button.new() register_link.text = "Don't have an account? Register" register_link.flat = true register_link.add_theme_font_size_override("font_size", 14) register_link.add_theme_color_override("font_color", Color(0.6, 0.7, 1.0)) register_link.add_theme_color_override("font_hover_color", Color(0.8, 0.85, 1.0)) register_link.pressed.connect(_on_register_pressed) links_container.add_child(register_link) # Forgot password link forgot_password_link = Button.new() forgot_password_link.text = "Forgot Password?" forgot_password_link.flat = true forgot_password_link.add_theme_font_size_override("font_size", 14) forgot_password_link.add_theme_color_override("font_color", Color(0.7, 0.7, 0.8)) forgot_password_link.add_theme_color_override("font_hover_color", Color(0.9, 0.9, 1.0)) forgot_password_link.pressed.connect(_on_forgot_password_pressed) links_container.add_child(forgot_password_link) func _create_back_button() -> void: var button_container = HBoxContainer.new() button_container.alignment = BoxContainer.ALIGNMENT_CENTER main_vbox.add_child(button_container) back_button = Button.new() back_button.text = "Back" back_button.custom_minimum_size = Vector2(100, 40) _style_button(back_button, false) back_button.pressed.connect(_on_back_pressed) button_container.add_child(back_button) # ======= STYLING ======= func _create_panel_style() -> StyleBoxFlat: var style = StyleBoxFlat.new() style.bg_color = Color(0.08, 0.08, 0.12, 1.0) style.set_border_width_all(0) style.set_corner_radius_all(0) return style func _create_separator_style() -> StyleBoxFlat: var style = StyleBoxFlat.new() style.bg_color = Color(0.5, 0.4, 0.2, 0.5) style.content_margin_top = 1 return style func _style_input(input: LineEdit) -> void: var style = StyleBoxFlat.new() style.bg_color = Color(0.12, 0.12, 0.16) style.border_color = Color(0.4, 0.35, 0.25) style.set_border_width_all(1) style.set_corner_radius_all(4) style.content_margin_left = 12 style.content_margin_right = 12 style.content_margin_top = 8 style.content_margin_bottom = 8 input.add_theme_stylebox_override("normal", style) var focus_style = style.duplicate() focus_style.border_color = Color(0.7, 0.6, 0.3) input.add_theme_stylebox_override("focus", focus_style) input.add_theme_color_override("font_color", Color(0.95, 0.9, 0.8)) input.add_theme_color_override("font_placeholder_color", Color(0.5, 0.5, 0.55)) input.add_theme_font_size_override("font_size", 16) func _style_button(button: Button, is_primary: bool) -> void: var style = StyleBoxFlat.new() if is_primary: style.bg_color = Color(0.3, 0.25, 0.15) style.border_color = Color(0.6, 0.5, 0.3) else: style.bg_color = Color(0.15, 0.15, 0.2) style.border_color = Color(0.4, 0.35, 0.25) style.set_border_width_all(2) style.set_corner_radius_all(6) style.content_margin_left = 20 style.content_margin_right = 20 style.content_margin_top = 10 style.content_margin_bottom = 10 button.add_theme_stylebox_override("normal", style) var hover_style = style.duplicate() if is_primary: hover_style.bg_color = Color(0.4, 0.35, 0.2) hover_style.border_color = Color(0.8, 0.7, 0.4) else: hover_style.bg_color = Color(0.2, 0.2, 0.25) hover_style.border_color = Color(0.5, 0.45, 0.35) button.add_theme_stylebox_override("hover", hover_style) var pressed_style = style.duplicate() pressed_style.bg_color = Color(0.1, 0.1, 0.12) button.add_theme_stylebox_override("pressed", pressed_style) var disabled_style = style.duplicate() disabled_style.bg_color = Color(0.1, 0.1, 0.12) disabled_style.border_color = Color(0.25, 0.25, 0.3) button.add_theme_stylebox_override("disabled", disabled_style) button.add_theme_color_override("font_color", Color(0.9, 0.85, 0.7)) button.add_theme_color_override("font_hover_color", Color(1.0, 0.95, 0.8)) button.add_theme_color_override("font_pressed_color", Color(0.7, 0.65, 0.55)) button.add_theme_color_override("font_disabled_color", Color(0.45, 0.42, 0.38)) button.add_theme_font_size_override("font_size", 18) # ======= EVENT HANDLERS ======= func _on_input_submitted(_text: String) -> void: _on_login_pressed() func _on_login_pressed() -> void: if _is_loading: return var email = email_input.text.strip_edges() var password = password_input.text # Validate inputs if email.is_empty(): _show_error("Please enter your email") return if password.is_empty(): _show_error("Please enter your password") return if not _is_valid_email(email): _show_error("Please enter a valid email address") return # Start login _set_loading(true) _hide_error() var result = await NetworkManager.login(email, password) _set_loading(false) if result.success: login_successful.emit(result.user) else: _show_error(result.message) func _on_register_pressed() -> void: register_requested.emit() func _on_forgot_password_pressed() -> void: forgot_password_requested.emit() func _on_back_pressed() -> void: back_pressed.emit() func _on_authenticated(user_data: Dictionary) -> void: login_successful.emit(user_data) func _on_auth_failed(error: String) -> void: _set_loading(false) _show_error(error) # ======= HELPERS ======= func _is_valid_email(email: String) -> bool: var regex = RegEx.new() regex.compile("^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$") return regex.search(email) != null func _show_error(message: String) -> void: error_label.text = message error_label.visible = true func _hide_error() -> void: error_label.text = "" error_label.visible = false func _set_loading(loading: bool) -> void: _is_loading = loading login_button.disabled = loading login_button.text = "Logging in..." if loading else "Login" email_input.editable = not loading password_input.editable = not loading func clear_form() -> void: email_input.text = "" password_input.text = "" _hide_error() _set_loading(false) func focus_email() -> void: email_input.grab_focus()