feature updates
This commit is contained in:
371
scripts/ui/LoginScreen.gd
Normal file
371
scripts/ui/LoginScreen.gd
Normal file
@@ -0,0 +1,371 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user