first commit
This commit is contained in:
209
godot_client/scenes/auth/login.gd
Normal file
209
godot_client/scenes/auth/login.gd
Normal file
@@ -0,0 +1,209 @@
|
||||
extends Control
|
||||
## Login Screen Script
|
||||
##
|
||||
## Handles user authentication via the backend API.
|
||||
## Validates input, displays errors, and navigates to character list on success.
|
||||
|
||||
# Node references
|
||||
@onready var email_input: LineEdit = $CenterContainer/LoginCard/MainContainer/ContentVBox/EmailInput
|
||||
@onready var password_input: LineEdit = $CenterContainer/LoginCard/MainContainer/ContentVBox/PasswordInput
|
||||
@onready var login_button: Button = $CenterContainer/LoginCard/MainContainer/ContentVBox/LoginButton
|
||||
@onready var error_label: Label = $CenterContainer/LoginCard/MainContainer/ContentVBox/ErrorLabel
|
||||
@onready var remember_checkbox: CheckBox = $CenterContainer/LoginCard/MainContainer/ContentVBox/RememberCheckBox
|
||||
@onready var register_link: Button = $CenterContainer/LoginCard/MainContainer/ContentVBox/BottomLinksVBox/RegisterLink
|
||||
@onready var forgot_password_link: Button = $CenterContainer/LoginCard/MainContainer/ContentVBox/BottomLinksVBox/ForgotPasswordLink
|
||||
|
||||
# Service references
|
||||
@onready var http_client: Node = get_node("/root/HTTPClient")
|
||||
@onready var state_manager: Node = get_node("/root/StateManager")
|
||||
|
||||
# Internal state
|
||||
var _is_loading: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
print("[LoginScreen] Initialized")
|
||||
|
||||
# Connect button signals
|
||||
login_button.pressed.connect(_on_login_button_pressed)
|
||||
register_link.pressed.connect(_on_register_link_pressed)
|
||||
forgot_password_link.pressed.connect(_on_forgot_password_link_pressed)
|
||||
|
||||
# Connect Enter key to submit
|
||||
email_input.text_submitted.connect(_on_input_submitted)
|
||||
password_input.text_submitted.connect(_on_input_submitted)
|
||||
|
||||
# Hide error label by default
|
||||
error_label.visible = false
|
||||
|
||||
# Load saved remember setting
|
||||
remember_checkbox.button_pressed = state_manager.get_setting("remember_login", true)
|
||||
|
||||
|
||||
## Handle login button press
|
||||
func _on_login_button_pressed() -> void:
|
||||
# Prevent double-click
|
||||
if _is_loading:
|
||||
return
|
||||
|
||||
# Get input values
|
||||
var email := email_input.text.strip_edges()
|
||||
var password := password_input.text
|
||||
|
||||
# Validate inputs
|
||||
var validation_error := _validate_inputs(email, password)
|
||||
if not validation_error.is_empty():
|
||||
_show_error(validation_error)
|
||||
return
|
||||
|
||||
# Start login process
|
||||
_start_login(email, password)
|
||||
|
||||
|
||||
## Handle Enter key press in input fields
|
||||
func _on_input_submitted(_text: String = "") -> void:
|
||||
_on_login_button_pressed()
|
||||
|
||||
|
||||
## Validate email and password
|
||||
func _validate_inputs(email: String, password: String) -> String:
|
||||
# Check if email is empty
|
||||
if email.is_empty():
|
||||
return "Please enter your email address"
|
||||
|
||||
# Check if password is empty
|
||||
if password.is_empty():
|
||||
return "Please enter your password"
|
||||
|
||||
# Basic email format validation (contains @ and .)
|
||||
if not email.contains("@") or not email.contains("."):
|
||||
return "Please enter a valid email address"
|
||||
|
||||
# Check minimum password length
|
||||
if password.length() < 6:
|
||||
return "Password must be at least 6 characters"
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
## Start login process
|
||||
func _start_login(email: String, password: String) -> void:
|
||||
_is_loading = true
|
||||
_hide_error()
|
||||
|
||||
# Disable button and show loading state
|
||||
login_button.disabled = true
|
||||
login_button.text = "LOGGING IN..."
|
||||
|
||||
# Build request payload
|
||||
var payload := {
|
||||
"email": email,
|
||||
"password": password,
|
||||
"remember_me": remember_checkbox.button_pressed
|
||||
}
|
||||
|
||||
print("[LoginScreen] Attempting login for: %s (remember_me=%s)" % [email, remember_checkbox.button_pressed])
|
||||
|
||||
# Make API request
|
||||
http_client.http_post(
|
||||
"/api/v1/auth/login",
|
||||
payload,
|
||||
_on_login_success,
|
||||
_on_login_error
|
||||
)
|
||||
|
||||
|
||||
## Handle successful login response
|
||||
func _on_login_success(response: APIResponse) -> void:
|
||||
_is_loading = false
|
||||
|
||||
# Re-enable button
|
||||
login_button.disabled = false
|
||||
login_button.text = "LOGIN"
|
||||
|
||||
# Check if response is actually successful
|
||||
if not response.is_success():
|
||||
_on_login_error(response)
|
||||
return
|
||||
|
||||
print("[LoginScreen] Login successful")
|
||||
|
||||
# Extract user data from response
|
||||
# Note: Authentication is cookie-based, so no token in response
|
||||
var result: Dictionary = response.result if response.result is Dictionary else {}
|
||||
var user_data: Dictionary = result.get("user", {})
|
||||
|
||||
if user_data.is_empty():
|
||||
_show_error("Invalid response from server")
|
||||
return
|
||||
|
||||
# Update remember setting
|
||||
state_manager.set_setting("remember_login", remember_checkbox.button_pressed)
|
||||
|
||||
# Save session to StateManager (cookie is already set in HTTPClient)
|
||||
state_manager.set_user_session(user_data)
|
||||
|
||||
print("[LoginScreen] User authenticated: %s" % user_data.get("email", "unknown"))
|
||||
print("[LoginScreen] Session cookie stored, ready for authenticated requests")
|
||||
|
||||
# Navigate back to main screen (which will show authenticated UI)
|
||||
print("[LoginScreen] Navigating to main screen...")
|
||||
get_tree().change_scene_to_file("res://scenes/main.tscn")
|
||||
|
||||
|
||||
## Handle login error
|
||||
func _on_login_error(response: APIResponse) -> void:
|
||||
_is_loading = false
|
||||
|
||||
# Re-enable button
|
||||
login_button.disabled = false
|
||||
login_button.text = "LOGIN"
|
||||
|
||||
# Get error message
|
||||
var error_message := response.get_error_message()
|
||||
|
||||
# Show user-friendly error
|
||||
if error_message.is_empty():
|
||||
error_message = "Login failed. Please try again."
|
||||
|
||||
# Handle specific error cases
|
||||
if response.status == 401:
|
||||
error_message = "Invalid email or password"
|
||||
elif response.status == 0 or response.status >= 500:
|
||||
error_message = "Cannot connect to server. Please check your connection."
|
||||
|
||||
print("[LoginScreen] Login error: %s (status=%d)" % [error_message, response.status])
|
||||
_show_error(error_message)
|
||||
|
||||
|
||||
## Show error message
|
||||
func _show_error(message: String) -> void:
|
||||
error_label.text = message
|
||||
error_label.visible = true
|
||||
|
||||
|
||||
## Hide error message
|
||||
func _hide_error() -> void:
|
||||
error_label.visible = false
|
||||
|
||||
|
||||
## Handle register link press
|
||||
func _on_register_link_pressed() -> void:
|
||||
print("[LoginScreen] Opening registration page in browser")
|
||||
var register_url := Settings.get_web_url() + "/auth/register"
|
||||
var error := OS.shell_open(register_url)
|
||||
|
||||
if error != OK:
|
||||
push_error("[LoginScreen] Failed to open browser: %s" % error)
|
||||
_show_error("Could not open registration page")
|
||||
|
||||
|
||||
## Handle forgot password link press
|
||||
func _on_forgot_password_link_pressed() -> void:
|
||||
print("[LoginScreen] Opening forgot password page in browser")
|
||||
var forgot_url := Settings.get_web_url() + "/auth/forgot-password"
|
||||
var error := OS.shell_open(forgot_url)
|
||||
|
||||
if error != OK:
|
||||
push_error("[LoginScreen] Failed to open browser: %s" % error)
|
||||
_show_error("Could not open password reset page")
|
||||
1
godot_client/scenes/auth/login.gd.uid
Normal file
1
godot_client/scenes/auth/login.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://260dpbodarqy
|
||||
241
godot_client/scenes/auth/login.tscn
Normal file
241
godot_client/scenes/auth/login.tscn
Normal file
@@ -0,0 +1,241 @@
|
||||
[gd_scene load_steps=10 format=3 uid="uid://pon554b5gdnu"]
|
||||
|
||||
[ext_resource type="Theme" uid="uid://bviqieumdiccr" path="res://assets/themes/main_theme.tres" id="1_8wugm"]
|
||||
[ext_resource type="Script" uid="uid://260dpbodarqy" path="res://scenes/auth/login.gd" id="1_lg6fp"]
|
||||
[ext_resource type="Texture2D" uid="uid://cja8kui47qb3d" path="res://assets/ui/main_menu.png" id="3_c4dse"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_card"]
|
||||
bg_color = Color(0, 0, 0, 0.9882353)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.247059, 0.247059, 0.278431, 1)
|
||||
corner_radius_top_left = 8
|
||||
corner_radius_top_right = 8
|
||||
corner_radius_bottom_right = 8
|
||||
corner_radius_bottom_left = 8
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_input_normal"]
|
||||
content_margin_left = 12.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 12.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.0862745, 0.129412, 0.243137, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.247059, 0.247059, 0.278431, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_input_focus"]
|
||||
content_margin_left = 12.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 12.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.0862745, 0.129412, 0.243137, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button_normal"]
|
||||
content_margin_left = 16.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 16.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.721569, 0.576471, 0.0392157, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button_pressed"]
|
||||
content_margin_left = 16.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 16.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.721569, 0.576471, 0.0392157, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.721569, 0.576471, 0.0392157, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button_hover"]
|
||||
content_margin_left = 16.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 16.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.956863, 0.815686, 0.247059, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.721569, 0.576471, 0.0392157, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[node name="Login" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("1_8wugm")
|
||||
script = ExtResource("1_lg6fp")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("3_c4dse")
|
||||
expand_mode = 2
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("1_8wugm")
|
||||
|
||||
[node name="LoginCard" type="PanelContainer" parent="CenterContainer"]
|
||||
custom_minimum_size = Vector2(400, 0)
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_card")
|
||||
|
||||
[node name="MainContainer" type="MarginContainer" parent="CenterContainer/LoginCard"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 32
|
||||
theme_override_constants/margin_top = 32
|
||||
theme_override_constants/margin_right = 32
|
||||
theme_override_constants/margin_bottom = 32
|
||||
|
||||
[node name="ContentVBox" type="VBoxContainer" parent="CenterContainer/LoginCard/MainContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 16
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "Welcome Back"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Spacer1" type="Control" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="EmailLabel" type="Label" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.894118, 0.894118, 0.905882, 1)
|
||||
text = "Email"
|
||||
|
||||
[node name="EmailInput" type="LineEdit" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.894118, 0.894118, 0.905882, 1)
|
||||
theme_override_colors/font_placeholder_color = Color(0.631373, 0.631373, 0.666667, 1)
|
||||
theme_override_colors/caret_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_input_normal")
|
||||
theme_override_styles/focus = SubResource("StyleBoxFlat_input_focus")
|
||||
placeholder_text = "Enter your email"
|
||||
|
||||
[node name="Spacer2" type="Control" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PasswordLabel" type="Label" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.894118, 0.894118, 0.905882, 1)
|
||||
text = "Password"
|
||||
|
||||
[node name="PasswordInput" type="LineEdit" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.894118, 0.894118, 0.905882, 1)
|
||||
theme_override_colors/font_placeholder_color = Color(0.631373, 0.631373, 0.666667, 1)
|
||||
theme_override_colors/caret_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_input_normal")
|
||||
theme_override_styles/focus = SubResource("StyleBoxFlat_input_focus")
|
||||
placeholder_text = "Enter your password"
|
||||
secret = true
|
||||
|
||||
[node name="Spacer3" type="Control" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 12)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RememberCheckBox" type="CheckBox" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.894118, 0.894118, 0.905882, 1)
|
||||
text = "Remember me"
|
||||
|
||||
[node name="Spacer4" type="Control" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ErrorLabel" type="Label" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(300, 0)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.937255, 0.266667, 0.266667, 1)
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="LoginButton" type="Button" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 44)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.101961, 0.101961, 0.180392, 1)
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button_normal")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_button_pressed")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_button_hover")
|
||||
text = "LOGIN"
|
||||
|
||||
[node name="Spacer5" type="Control" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 24)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="BottomLinksVBox" type="VBoxContainer" parent="CenterContainer/LoginCard/MainContainer/ContentVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
alignment = 1
|
||||
|
||||
[node name="RegisterLink" type="Button" parent="CenterContainer/LoginCard/MainContainer/ContentVBox/BottomLinksVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.631373, 0.631373, 0.666667, 1)
|
||||
theme_override_colors/font_hover_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
text = "Don't have an account? Register"
|
||||
flat = true
|
||||
|
||||
[node name="ForgotPasswordLink" type="Button" parent="CenterContainer/LoginCard/MainContainer/ContentVBox/BottomLinksVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.631373, 0.631373, 0.666667, 1)
|
||||
theme_override_colors/font_hover_color = Color(0.831373, 0.686275, 0.215686, 1)
|
||||
text = "Forgot password?"
|
||||
flat = true
|
||||
65
godot_client/scenes/character/character_card.tscn
Normal file
65
godot_client/scenes/character/character_card.tscn
Normal file
@@ -0,0 +1,65 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dqx8k3h5nwc2r"]
|
||||
|
||||
[ext_resource type="Theme" uid="uid://bviqieumdiccr" path="res://assets/themes/main_theme.tres" id="1_80ja4"]
|
||||
[ext_resource type="Script" uid="uid://sxgrib8ck0wx" path="res://scripts/character/character_card.gd" id="1_card"]
|
||||
|
||||
[node name="CharacterCard" type="PanelContainer"]
|
||||
custom_minimum_size = Vector2(0, 120)
|
||||
size_flags_horizontal = 3
|
||||
theme = ExtResource("1_80ja4")
|
||||
script = ExtResource("1_card")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 12
|
||||
theme_override_constants/margin_right = 16
|
||||
theme_override_constants/margin_bottom = 12
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="InfoVBox" type="VBoxContainer" parent="MarginContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="NameLabel" type="Label" parent="MarginContainer/HBoxContainer/InfoVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "Character Name"
|
||||
|
||||
[node name="ClassLabel" type="Label" parent="MarginContainer/HBoxContainer/InfoVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "Class Name"
|
||||
|
||||
[node name="LevelLabel" type="Label" parent="MarginContainer/HBoxContainer/InfoVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "Level 1"
|
||||
|
||||
[node name="GoldLabel" type="Label" parent="MarginContainer/HBoxContainer/InfoVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "0 Gold"
|
||||
|
||||
[node name="LocationLabel" type="Label" parent="MarginContainer/HBoxContainer/InfoVBox"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "Unknown Location"
|
||||
|
||||
[node name="ActionVBox" type="VBoxContainer" parent="MarginContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
alignment = 1
|
||||
|
||||
[node name="SelectButton" type="Button" parent="MarginContainer/HBoxContainer/ActionVBox"]
|
||||
custom_minimum_size = Vector2(100, 36)
|
||||
layout_mode = 2
|
||||
text = "Select"
|
||||
|
||||
[node name="DeleteButton" type="Button" parent="MarginContainer/HBoxContainer/ActionVBox"]
|
||||
custom_minimum_size = Vector2(100, 36)
|
||||
layout_mode = 2
|
||||
text = "Delete"
|
||||
156
godot_client/scenes/character/character_list.tscn
Normal file
156
godot_client/scenes/character/character_list.tscn
Normal file
@@ -0,0 +1,156 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://csqelun8tcd7y"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://x4mt6jwbywsl" path="res://scripts/character/character_list.gd" id="1_list"]
|
||||
[ext_resource type="Texture2D" uid="uid://cja8kui47qb3d" path="res://assets/ui/main_menu.png" id="2_arrgh"]
|
||||
[ext_resource type="Theme" uid="uid://bviqieumdiccr" path="res://assets/themes/main_theme.tres" id="3_pfk5o"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mvluj"]
|
||||
bg_color = Color(0, 0, 0, 0.69411767)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
border_color = Color(0.83137256, 0.6862745, 0.21568628, 1)
|
||||
|
||||
[node name="CharacterList" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_list")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("2_arrgh")
|
||||
expand_mode = 3
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 16
|
||||
|
||||
[node name="Header" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="VBoxContainer/Header"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "Your Characters"
|
||||
|
||||
[node name="TierLabel" type="Label" parent="VBoxContainer/Header"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 16
|
||||
text = "Free: 0/1"
|
||||
horizontal_alignment = 2
|
||||
|
||||
[node name="LoadingIndicator" type="CenterContainer" parent="VBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/LoadingIndicator"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ProgressBar" type="ProgressBar" parent="VBoxContainer/LoadingIndicator/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(200, 0)
|
||||
layout_mode = 2
|
||||
max_value = 0.0
|
||||
indeterminate = true
|
||||
editor_preview_indeterminate = false
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/LoadingIndicator/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Loading characters..."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="CharacterScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="CharacterVBox" type="VBoxContainer" parent="VBoxContainer/CharacterScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="ErrorContainer" type="MarginContainer" parent="VBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
theme = ExtResource("3_pfk5o")
|
||||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 16
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="ErrorLabel" type="Label" parent="VBoxContainer/ErrorContainer"]
|
||||
custom_minimum_size = Vector2(200, 20)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.9, 0.3, 0.3, 1)
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_mvluj")
|
||||
text = "Error message here"
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="ActionContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 16
|
||||
alignment = 1
|
||||
|
||||
[node name="CreateButton" type="Button" parent="VBoxContainer/ActionContainer"]
|
||||
custom_minimum_size = Vector2(180, 44)
|
||||
layout_mode = 2
|
||||
text = "Create Character"
|
||||
|
||||
[node name="RefreshButton" type="Button" parent="VBoxContainer/ActionContainer"]
|
||||
custom_minimum_size = Vector2(100, 44)
|
||||
layout_mode = 2
|
||||
text = "Refresh"
|
||||
|
||||
[node name="EmptyState" type="CenterContainer" parent="."]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="EmptyState"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 16
|
||||
|
||||
[node name="IconLabel" type="Label" parent="EmptyState/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 48
|
||||
text = "⚔"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="MessageLabel" type="Label" parent="EmptyState/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "No characters yet"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="SubMessageLabel" type="Label" parent="EmptyState/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Create your first character to begin your adventure!"
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="CreateFirstButton" type="Button" parent="EmptyState/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(180, 44)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
text = "Create Character"
|
||||
335
godot_client/scenes/components/README.md
Normal file
335
godot_client/scenes/components/README.md
Normal file
@@ -0,0 +1,335 @@
|
||||
# Reusable UI Components
|
||||
|
||||
This directory contains reusable UI components for Code of Conquest.
|
||||
|
||||
## Available Components
|
||||
|
||||
### CustomButton
|
||||
**Script**: `scripts/components/custom_button.gd`
|
||||
|
||||
Enhanced button with multiple visual variants and features.
|
||||
|
||||
**Features**:
|
||||
- Multiple variants (Primary, Secondary, Danger, Success, Ghost)
|
||||
- Icon support (left or right position)
|
||||
- Loading state
|
||||
- Hover effects
|
||||
- Themed styling
|
||||
|
||||
**Usage**:
|
||||
```gdscript
|
||||
var btn = CustomButton.new()
|
||||
btn.text = "Login"
|
||||
btn.set_variant(CustomButton.Variant.PRIMARY)
|
||||
btn.button_clicked.connect(_on_login_clicked)
|
||||
add_child(btn)
|
||||
```
|
||||
|
||||
**Variants**:
|
||||
- `PRIMARY` - Gold accent button for main actions
|
||||
- `SECONDARY` - Standard button
|
||||
- `DANGER` - Red button for destructive actions (delete, etc.)
|
||||
- `SUCCESS` - Green button for positive actions
|
||||
- `GHOST` - Transparent/subtle button
|
||||
|
||||
### Card
|
||||
**Script**: `scripts/components/card.gd`
|
||||
|
||||
Container with optional header and footer, styled like the web UI cards.
|
||||
|
||||
**Features**:
|
||||
- Optional header with text
|
||||
- Body content area
|
||||
- Optional footer with buttons
|
||||
- Multiple style variants
|
||||
- Automatic sizing
|
||||
|
||||
**Usage**:
|
||||
```gdscript
|
||||
var card = Card.new()
|
||||
card.set_header("Character Details")
|
||||
|
||||
var content = Label.new()
|
||||
content.text = "Character information goes here"
|
||||
card.add_content(content)
|
||||
|
||||
card.set_footer_buttons(["Save", "Cancel"])
|
||||
card.footer_button_pressed.connect(_on_card_button_pressed)
|
||||
|
||||
add_child(card)
|
||||
```
|
||||
|
||||
**Style Variants**:
|
||||
- `DEFAULT` - Standard card with subtle border
|
||||
- `HIGHLIGHTED` - Gold border with shadow
|
||||
- `SUBTLE` - Minimal border
|
||||
|
||||
### FormField
|
||||
**Script**: `scripts/components/form_field.gd`
|
||||
|
||||
Form input field with label, validation, and error display.
|
||||
|
||||
**Features**:
|
||||
- Label + input + error message
|
||||
- Multiple input types (text, email, password, number, phone)
|
||||
- Built-in validation
|
||||
- Required field support
|
||||
- Min/max length validation
|
||||
- Error state styling
|
||||
|
||||
**Usage**:
|
||||
```gdscript
|
||||
var email_field = FormField.new()
|
||||
email_field.set_label("Email")
|
||||
email_field.set_placeholder("Enter your email")
|
||||
email_field.input_type = FormField.InputType.EMAIL
|
||||
email_field.required = true
|
||||
email_field.value_changed.connect(_on_email_changed)
|
||||
|
||||
add_child(email_field)
|
||||
|
||||
# Later, validate
|
||||
if email_field.validate():
|
||||
var email = email_field.get_value()
|
||||
# Process email
|
||||
```
|
||||
|
||||
**Input Types**:
|
||||
- `TEXT` - Plain text
|
||||
- `EMAIL` - Email validation
|
||||
- `PASSWORD` - Hidden password
|
||||
- `NUMBER` - Numeric only
|
||||
- `PHONE` - Phone number format
|
||||
|
||||
## Creating New Components
|
||||
|
||||
### 1. Create Script
|
||||
|
||||
Create a new GDScript file in `scripts/components/`:
|
||||
|
||||
```gdscript
|
||||
extends Control # or appropriate base class
|
||||
class_name MyComponent
|
||||
|
||||
## MyComponent
|
||||
##
|
||||
## Brief description of what this component does
|
||||
##
|
||||
## Usage:
|
||||
## var comp = MyComponent.new()
|
||||
## comp.some_property = "value"
|
||||
|
||||
signal some_signal(data: String)
|
||||
|
||||
@export var some_property: String = ""
|
||||
|
||||
func _ready() -> void:
|
||||
_setup_structure()
|
||||
_apply_styling()
|
||||
|
||||
func _setup_structure() -> void:
|
||||
# Build node hierarchy
|
||||
pass
|
||||
|
||||
func _apply_styling() -> void:
|
||||
# Apply theme colors and styleboxes
|
||||
pass
|
||||
```
|
||||
|
||||
### 2. Use Theme Colors
|
||||
|
||||
Always use `ThemeColors` constants:
|
||||
|
||||
```gdscript
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = ThemeColors.BACKGROUND_CARD
|
||||
style.border_color = ThemeColors.BORDER_DEFAULT
|
||||
```
|
||||
|
||||
### 3. Make It Configurable
|
||||
|
||||
Use `@export` variables for Godot Inspector:
|
||||
|
||||
```gdscript
|
||||
@export var title: String = ""
|
||||
@export_enum("Small", "Medium", "Large") var size: String = "Medium"
|
||||
@export var show_icon: bool = true
|
||||
```
|
||||
|
||||
### 4. Emit Signals
|
||||
|
||||
For user interactions:
|
||||
|
||||
```gdscript
|
||||
signal item_selected(item_id: String)
|
||||
signal value_changed(new_value: Variant)
|
||||
|
||||
func _on_internal_action():
|
||||
item_selected.emit("some_id")
|
||||
```
|
||||
|
||||
### 5. Document Usage
|
||||
|
||||
Include usage examples in docstring:
|
||||
|
||||
```gdscript
|
||||
## MyComponent
|
||||
##
|
||||
## Detailed description of the component.
|
||||
##
|
||||
## Usage:
|
||||
## var comp = MyComponent.new()
|
||||
## comp.title = "My Title"
|
||||
## comp.item_selected.connect(_on_item_selected)
|
||||
## add_child(comp)
|
||||
```
|
||||
|
||||
## Component Patterns
|
||||
|
||||
### Separation of Structure and Style
|
||||
|
||||
```gdscript
|
||||
func _ready() -> void:
|
||||
_setup_structure() # Build node hierarchy
|
||||
_apply_styling() # Apply theme
|
||||
|
||||
func _setup_structure() -> void:
|
||||
# Create child nodes
|
||||
# Set up hierarchy
|
||||
# Connect signals
|
||||
pass
|
||||
|
||||
func _apply_styling() -> void:
|
||||
# Apply StyleBoxes
|
||||
# Set colors
|
||||
# Set fonts
|
||||
pass
|
||||
```
|
||||
|
||||
### Responsive Design
|
||||
|
||||
```gdscript
|
||||
func _apply_styling() -> void:
|
||||
# Check platform
|
||||
var is_mobile = OS.get_name() in ["Android", "iOS"]
|
||||
|
||||
if is_mobile:
|
||||
# Larger touch targets
|
||||
custom_minimum_size = Vector2(60, 60)
|
||||
else:
|
||||
# Desktop sizing
|
||||
custom_minimum_size = Vector2(40, 40)
|
||||
```
|
||||
|
||||
### Validation Pattern
|
||||
|
||||
```gdscript
|
||||
func validate() -> bool:
|
||||
var is_valid = true
|
||||
|
||||
# Check conditions
|
||||
if some_condition:
|
||||
show_error("Error message")
|
||||
is_valid = false
|
||||
|
||||
if is_valid:
|
||||
clear_error()
|
||||
|
||||
return is_valid
|
||||
```
|
||||
|
||||
## Testing Components
|
||||
|
||||
Create a test scene `test_components.tscn`:
|
||||
|
||||
1. Add each component
|
||||
2. Test all variants
|
||||
3. Test all states (normal, hover, pressed, disabled)
|
||||
4. Test on different screen sizes
|
||||
5. Test on different platforms
|
||||
|
||||
Example test scene structure:
|
||||
```
|
||||
Control (root)
|
||||
├─ VBoxContainer
|
||||
│ ├─ Label ("Buttons")
|
||||
│ ├─ HBoxContainer
|
||||
│ │ ├─ CustomButton (Primary)
|
||||
│ │ ├─ CustomButton (Secondary)
|
||||
│ │ ├─ CustomButton (Danger)
|
||||
│ │ └─ CustomButton (Ghost)
|
||||
│ ├─ Label ("Cards")
|
||||
│ ├─ HBoxContainer
|
||||
│ │ ├─ Card (Default)
|
||||
│ │ ├─ Card (Highlighted)
|
||||
│ │ └─ Card (Subtle)
|
||||
│ └─ Label ("Form Fields")
|
||||
│ ├─ FormField (Text)
|
||||
│ ├─ FormField (Email)
|
||||
│ └─ FormField (Password)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use ThemeColors** - Never hardcode colors
|
||||
2. **Make components reusable** - Avoid scene-specific logic
|
||||
3. **Use signals for communication** - Don't couple components
|
||||
4. **Document everything** - Docstrings + usage examples
|
||||
5. **Test on all platforms** - Desktop, mobile, web
|
||||
6. **Follow naming conventions** - PascalCase for class names
|
||||
7. **Export important properties** - Make them editable in Inspector
|
||||
8. **Validate inputs** - Check types and ranges
|
||||
9. **Handle edge cases** - Empty strings, null values, etc.
|
||||
10. **Keep it simple** - One component, one responsibility
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### ❌ Don't hardcode colors
|
||||
```gdscript
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = Color("#1a1a2e") # BAD
|
||||
```
|
||||
|
||||
### ✅ Use ThemeColors
|
||||
```gdscript
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = ThemeColors.BACKGROUND_PRIMARY # GOOD
|
||||
```
|
||||
|
||||
### ❌ Don't create nodes in every frame
|
||||
```gdscript
|
||||
func _process(delta):
|
||||
var button = Button.new() # BAD - leaks memory
|
||||
add_child(button)
|
||||
```
|
||||
|
||||
### ✅ Create once in _ready
|
||||
```gdscript
|
||||
var button: Button
|
||||
|
||||
func _ready():
|
||||
button = Button.new()
|
||||
add_child(button)
|
||||
```
|
||||
|
||||
### ❌ Don't couple components
|
||||
```gdscript
|
||||
# In a button component
|
||||
func _on_pressed():
|
||||
get_parent().get_node("SomeOtherNode").do_something() # BAD
|
||||
```
|
||||
|
||||
### ✅ Use signals
|
||||
```gdscript
|
||||
signal action_requested
|
||||
|
||||
func _on_pressed():
|
||||
action_requested.emit() # GOOD
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Godot UI Documentation](https://docs.godotengine.org/en/stable/tutorials/ui/index.html)
|
||||
- [Control Nodes](https://docs.godotengine.org/en/stable/classes/class_control.html)
|
||||
- [Themes](https://docs.godotengine.org/en/stable/tutorials/ui/gui_using_theme_editor.html)
|
||||
- [StyleBox](https://docs.godotengine.org/en/stable/classes/class_stylebox.html)
|
||||
183
godot_client/scenes/main.tscn
Normal file
183
godot_client/scenes/main.tscn
Normal file
@@ -0,0 +1,183 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://b8qxqvw3n2k4r"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d100mupmyal5" path="res://scripts/main.gd" id="1_main"]
|
||||
[ext_resource type="Theme" uid="uid://bviqieumdiccr" path="res://assets/themes/main_theme.tres" id="1_sugp2"]
|
||||
[ext_resource type="Texture2D" uid="uid://cja8kui47qb3d" path="res://assets/ui/main_menu.png" id="3_sugp2"]
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_0wfyh"]
|
||||
script/source = "extends Button
|
||||
class_name CustomButton
|
||||
## Custom Button Component
|
||||
##
|
||||
## Enhanced button with icon support, loading state, and themed styling.
|
||||
##
|
||||
## Features:
|
||||
## - Optional icon (left or right)
|
||||
## - Loading spinner state
|
||||
## - Hover effects
|
||||
## - Disabled state management
|
||||
## - Multiple visual variants
|
||||
##
|
||||
## Usage:
|
||||
## var btn = CustomButton.new()
|
||||
## btn.text = \"Login\"
|
||||
## btn.variant = CustomButton.Variant.PRIMARY
|
||||
## btn.set_loading(true)
|
||||
|
||||
signal button_clicked()
|
||||
|
||||
# Button variants
|
||||
enum Variant {
|
||||
PRIMARY, # Gold accent button (main actions)
|
||||
SECONDARY, # Standard button
|
||||
DANGER, # Red button (destructive actions)
|
||||
SUCCESS, # Green button (positive actions)
|
||||
GHOST # Transparent button (subtle actions)
|
||||
}
|
||||
|
||||
# Icon position
|
||||
enum IconPosition {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
NONE
|
||||
}
|
||||
|
||||
# Export variables (editable in Godot Inspector)
|
||||
@export var variant: Variant = Variant.SECONDARY
|
||||
@export var icon_texture: Texture2D = null
|
||||
@export var icon_position: IconPosition = IconPosition.LEFT
|
||||
@export var show_loading: bool = false
|
||||
|
||||
# Internal nodes (set up in _ready)
|
||||
#var _icon: TextureRect = null
|
||||
#var _label: Label = null
|
||||
#var _spinner: TextureRect = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# Apply theme based on variant
|
||||
|
||||
|
||||
# Connect press signal
|
||||
pressed.connect(_on_button_pressed)
|
||||
|
||||
|
||||
|
||||
## Set loading state
|
||||
func set_loading(loading: bool) -> void:
|
||||
show_loading = loading
|
||||
disabled = loading
|
||||
_update_loading_state()
|
||||
|
||||
|
||||
|
||||
## Internal: Update loading spinner
|
||||
func _update_loading_state() -> void:
|
||||
# TODO: Implement loading spinner when scene is set up
|
||||
if show_loading:
|
||||
text = \"Loading...\"
|
||||
else:
|
||||
# Restore original text
|
||||
pass
|
||||
|
||||
|
||||
## Internal: Handle button press
|
||||
func _on_button_pressed() -> void:
|
||||
if not disabled and not show_loading:
|
||||
button_clicked.emit()
|
||||
"
|
||||
|
||||
[node name="Main" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("1_sugp2")
|
||||
script = ExtResource("1_main")
|
||||
|
||||
[node name="BackgroundPanel" type="Panel" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="BackgroundPanel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("3_sugp2")
|
||||
expand_mode = 4
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="CenterContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="CenterContainer/PanelContainer"]
|
||||
layout_mode = 2
|
||||
theme = ExtResource("1_sugp2")
|
||||
theme_override_constants/margin_left = 15
|
||||
theme_override_constants/margin_top = 15
|
||||
theme_override_constants/margin_right = 15
|
||||
theme_override_constants/margin_bottom = 15
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/PanelContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 20
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Code of Conquest"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Spacer1" type="Control" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="WelcomeLabel" type="Label" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Welcome, Player"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="StatusLabel" type="Label" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Loading..."
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Spacer2" type="Control" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 30)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ButtonContainer" type="HBoxContainer" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 16
|
||||
alignment = 1
|
||||
|
||||
[node name="PlayNowButton" type="Button" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContainer/ButtonContainer"]
|
||||
custom_minimum_size = Vector2(150, 0)
|
||||
layout_mode = 2
|
||||
text = "Play Now"
|
||||
script = SubResource("GDScript_0wfyh")
|
||||
|
||||
[node name="LogoutButton" type="Button" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContainer/ButtonContainer"]
|
||||
custom_minimum_size = Vector2(150, 0)
|
||||
layout_mode = 2
|
||||
text = "Logout"
|
||||
script = SubResource("GDScript_0wfyh")
|
||||
21
godot_client/scenes/test_services.tscn
Normal file
21
godot_client/scenes/test_services.tscn
Normal file
@@ -0,0 +1,21 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b1gbhqqbu5aij"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c1uv0pqjtun0r" path="res://scripts/test_services.gd" id="1_slw0k"]
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="."]
|
||||
layout_mode = 0
|
||||
offset_right = 55.0
|
||||
offset_bottom = 40.0
|
||||
|
||||
[node name="Button" type="Button" parent="PanelContainer"]
|
||||
layout_mode = 2
|
||||
text = "Test API"
|
||||
script = ExtResource("1_slw0k")
|
||||
Reference in New Issue
Block a user