Files
FFCardGame/scripts/ui/LoginScreen.gd
2026-02-02 16:28:53 -05:00

372 lines
10 KiB
GDScript

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()