372 lines
10 KiB
GDScript
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()
|