feature updates
This commit is contained in:
494
scripts/ui/ProfileScreen.gd
Normal file
494
scripts/ui/ProfileScreen.gd
Normal file
@@ -0,0 +1,494 @@
|
||||
class_name ProfileScreen
|
||||
extends CanvasLayer
|
||||
|
||||
## ProfileScreen - Displays player stats and match history
|
||||
|
||||
signal back_pressed
|
||||
|
||||
# Window dimensions
|
||||
const WINDOW_SIZE := Vector2i(600, 700)
|
||||
|
||||
# Pagination
|
||||
const MATCHES_PER_PAGE = 10
|
||||
|
||||
# UI Components
|
||||
var main_container: VBoxContainer
|
||||
var back_button: Button
|
||||
var username_label: Label
|
||||
var stats_container: HBoxContainer
|
||||
var elo_value: Label
|
||||
var wins_value: Label
|
||||
var losses_value: Label
|
||||
var winrate_value: Label
|
||||
var games_value: Label
|
||||
var history_section: PanelContainer
|
||||
var history_list: VBoxContainer
|
||||
var pagination_container: HBoxContainer
|
||||
var prev_button: Button
|
||||
var page_label: Label
|
||||
var next_button: Button
|
||||
var loading_label: Label
|
||||
var error_label: Label
|
||||
|
||||
# State
|
||||
var current_page: int = 0
|
||||
var total_matches: int = 0
|
||||
var is_loading: bool = false
|
||||
var matches_cache: Array = []
|
||||
|
||||
# Styling
|
||||
var custom_font: Font = preload("res://JimNightshade-Regular.ttf")
|
||||
const BG_COLOR := Color(0.12, 0.11, 0.15, 1.0)
|
||||
const PANEL_COLOR := Color(0.18, 0.16, 0.22, 1.0)
|
||||
const ACCENT_COLOR := Color(0.4, 0.35, 0.55, 1.0)
|
||||
const TEXT_COLOR := Color(0.9, 0.88, 0.82, 1.0)
|
||||
const MUTED_COLOR := Color(0.6, 0.58, 0.52, 1.0)
|
||||
const WIN_COLOR := Color(0.4, 0.8, 0.4, 1.0)
|
||||
const LOSS_COLOR := Color(0.8, 0.4, 0.4, 1.0)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_create_ui()
|
||||
_load_profile()
|
||||
|
||||
|
||||
func _create_ui() -> void:
|
||||
# Background
|
||||
var bg = ColorRect.new()
|
||||
bg.color = BG_COLOR
|
||||
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
add_child(bg)
|
||||
|
||||
# Main container
|
||||
main_container = VBoxContainer.new()
|
||||
main_container.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
main_container.add_theme_constant_override("separation", 16)
|
||||
add_child(main_container)
|
||||
|
||||
var margin = MarginContainer.new()
|
||||
margin.add_theme_constant_override("margin_left", 24)
|
||||
margin.add_theme_constant_override("margin_right", 24)
|
||||
margin.add_theme_constant_override("margin_top", 16)
|
||||
margin.add_theme_constant_override("margin_bottom", 16)
|
||||
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
main_container.add_child(margin)
|
||||
|
||||
var content = VBoxContainer.new()
|
||||
content.add_theme_constant_override("separation", 16)
|
||||
margin.add_child(content)
|
||||
|
||||
# Back button
|
||||
back_button = _create_button("< Back", false)
|
||||
back_button.custom_minimum_size = Vector2(80, 32)
|
||||
back_button.pressed.connect(_on_back_pressed)
|
||||
content.add_child(back_button)
|
||||
|
||||
# Title
|
||||
var title = Label.new()
|
||||
title.add_theme_font_override("font", custom_font)
|
||||
title.add_theme_font_size_override("font_size", 28)
|
||||
title.add_theme_color_override("font_color", TEXT_COLOR)
|
||||
title.text = "PLAYER PROFILE"
|
||||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
content.add_child(title)
|
||||
|
||||
# Username
|
||||
username_label = Label.new()
|
||||
username_label.add_theme_font_override("font", custom_font)
|
||||
username_label.add_theme_font_size_override("font_size", 22)
|
||||
username_label.add_theme_color_override("font_color", ACCENT_COLOR)
|
||||
username_label.text = "Loading..."
|
||||
username_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
content.add_child(username_label)
|
||||
|
||||
# Stats section
|
||||
_create_stats_section(content)
|
||||
|
||||
# Match history section
|
||||
_create_history_section(content)
|
||||
|
||||
# Error label
|
||||
error_label = Label.new()
|
||||
error_label.add_theme_font_override("font", custom_font)
|
||||
error_label.add_theme_font_size_override("font_size", 14)
|
||||
error_label.add_theme_color_override("font_color", Color(0.9, 0.3, 0.3))
|
||||
error_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
error_label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
error_label.visible = false
|
||||
content.add_child(error_label)
|
||||
|
||||
|
||||
func _create_stats_section(parent: VBoxContainer) -> void:
|
||||
var stats_panel = _create_panel_section("STATISTICS")
|
||||
parent.add_child(stats_panel)
|
||||
|
||||
stats_container = HBoxContainer.new()
|
||||
stats_container.add_theme_constant_override("separation", 24)
|
||||
stats_panel.get_child(0).add_child(stats_container)
|
||||
|
||||
# Create stat boxes
|
||||
var elo_box = _create_stat_box("ELO RATING")
|
||||
elo_value = elo_box.get_child(1)
|
||||
stats_container.add_child(elo_box)
|
||||
|
||||
var wins_box = _create_stat_box("WINS")
|
||||
wins_value = wins_box.get_child(1)
|
||||
stats_container.add_child(wins_box)
|
||||
|
||||
var losses_box = _create_stat_box("LOSSES")
|
||||
losses_value = losses_box.get_child(1)
|
||||
stats_container.add_child(losses_box)
|
||||
|
||||
var winrate_box = _create_stat_box("WIN RATE")
|
||||
winrate_value = winrate_box.get_child(1)
|
||||
stats_container.add_child(winrate_box)
|
||||
|
||||
var games_box = _create_stat_box("GAMES")
|
||||
games_value = games_box.get_child(1)
|
||||
stats_container.add_child(games_box)
|
||||
|
||||
|
||||
func _create_stat_box(title: String) -> VBoxContainer:
|
||||
var box = VBoxContainer.new()
|
||||
box.add_theme_constant_override("separation", 4)
|
||||
box.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
|
||||
var title_label = Label.new()
|
||||
title_label.add_theme_font_override("font", custom_font)
|
||||
title_label.add_theme_font_size_override("font_size", 12)
|
||||
title_label.add_theme_color_override("font_color", MUTED_COLOR)
|
||||
title_label.text = title
|
||||
title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
box.add_child(title_label)
|
||||
|
||||
var value_label = Label.new()
|
||||
value_label.add_theme_font_override("font", custom_font)
|
||||
value_label.add_theme_font_size_override("font_size", 24)
|
||||
value_label.add_theme_color_override("font_color", TEXT_COLOR)
|
||||
value_label.text = "-"
|
||||
value_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
box.add_child(value_label)
|
||||
|
||||
return box
|
||||
|
||||
|
||||
func _create_history_section(parent: VBoxContainer) -> void:
|
||||
history_section = _create_panel_section("MATCH HISTORY")
|
||||
history_section.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
parent.add_child(history_section)
|
||||
|
||||
var content = history_section.get_child(0) as VBoxContainer
|
||||
|
||||
# Loading indicator
|
||||
loading_label = Label.new()
|
||||
loading_label.add_theme_font_override("font", custom_font)
|
||||
loading_label.add_theme_font_size_override("font_size", 14)
|
||||
loading_label.add_theme_color_override("font_color", MUTED_COLOR)
|
||||
loading_label.text = "Loading..."
|
||||
loading_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
content.add_child(loading_label)
|
||||
|
||||
# Match list
|
||||
history_list = VBoxContainer.new()
|
||||
history_list.add_theme_constant_override("separation", 8)
|
||||
history_list.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
content.add_child(history_list)
|
||||
|
||||
# Pagination
|
||||
pagination_container = HBoxContainer.new()
|
||||
pagination_container.add_theme_constant_override("separation", 16)
|
||||
pagination_container.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
content.add_child(pagination_container)
|
||||
|
||||
prev_button = _create_button("< Prev", false)
|
||||
prev_button.custom_minimum_size = Vector2(80, 32)
|
||||
prev_button.pressed.connect(_on_prev_page)
|
||||
pagination_container.add_child(prev_button)
|
||||
|
||||
page_label = Label.new()
|
||||
page_label.add_theme_font_override("font", custom_font)
|
||||
page_label.add_theme_font_size_override("font_size", 14)
|
||||
page_label.add_theme_color_override("font_color", TEXT_COLOR)
|
||||
page_label.text = "Page 1"
|
||||
pagination_container.add_child(page_label)
|
||||
|
||||
next_button = _create_button("Next >", false)
|
||||
next_button.custom_minimum_size = Vector2(80, 32)
|
||||
next_button.pressed.connect(_on_next_page)
|
||||
pagination_container.add_child(next_button)
|
||||
|
||||
|
||||
func _create_panel_section(title: String) -> PanelContainer:
|
||||
var panel = PanelContainer.new()
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = PANEL_COLOR
|
||||
style.set_corner_radius_all(8)
|
||||
style.set_content_margin_all(16)
|
||||
panel.add_theme_stylebox_override("panel", style)
|
||||
|
||||
var vbox = VBoxContainer.new()
|
||||
vbox.add_theme_constant_override("separation", 12)
|
||||
panel.add_child(vbox)
|
||||
|
||||
var title_label = Label.new()
|
||||
title_label.add_theme_font_override("font", custom_font)
|
||||
title_label.add_theme_font_size_override("font_size", 18)
|
||||
title_label.add_theme_color_override("font_color", TEXT_COLOR)
|
||||
title_label.text = title
|
||||
title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
vbox.add_child(title_label)
|
||||
|
||||
return panel
|
||||
|
||||
|
||||
func _create_button(text: String, primary: bool) -> Button:
|
||||
var button = Button.new()
|
||||
button.text = text
|
||||
button.add_theme_font_override("font", custom_font)
|
||||
button.add_theme_font_size_override("font_size", 16)
|
||||
button.custom_minimum_size = Vector2(0, 40)
|
||||
|
||||
var normal = StyleBoxFlat.new()
|
||||
var hover = StyleBoxFlat.new()
|
||||
var pressed = StyleBoxFlat.new()
|
||||
var disabled = StyleBoxFlat.new()
|
||||
|
||||
if primary:
|
||||
normal.bg_color = ACCENT_COLOR
|
||||
hover.bg_color = ACCENT_COLOR.lightened(0.15)
|
||||
pressed.bg_color = ACCENT_COLOR.darkened(0.15)
|
||||
button.add_theme_color_override("font_color", Color.WHITE)
|
||||
button.add_theme_color_override("font_hover_color", Color.WHITE)
|
||||
else:
|
||||
normal.bg_color = Color(0.25, 0.23, 0.3)
|
||||
hover.bg_color = Color(0.3, 0.28, 0.35)
|
||||
pressed.bg_color = Color(0.2, 0.18, 0.25)
|
||||
button.add_theme_color_override("font_color", TEXT_COLOR)
|
||||
button.add_theme_color_override("font_hover_color", TEXT_COLOR)
|
||||
|
||||
disabled.bg_color = Color(0.2, 0.18, 0.22)
|
||||
button.add_theme_color_override("font_disabled_color", MUTED_COLOR)
|
||||
|
||||
for style in [normal, hover, pressed, disabled]:
|
||||
style.set_corner_radius_all(6)
|
||||
style.set_content_margin_all(8)
|
||||
|
||||
button.add_theme_stylebox_override("normal", normal)
|
||||
button.add_theme_stylebox_override("hover", hover)
|
||||
button.add_theme_stylebox_override("pressed", pressed)
|
||||
button.add_theme_stylebox_override("disabled", disabled)
|
||||
|
||||
return button
|
||||
|
||||
|
||||
func _load_profile() -> void:
|
||||
if not NetworkManager or not NetworkManager.is_authenticated:
|
||||
_show_error("Not logged in")
|
||||
return
|
||||
|
||||
is_loading = true
|
||||
loading_label.visible = true
|
||||
|
||||
var result = await NetworkManager.get_profile()
|
||||
|
||||
if result.success:
|
||||
_update_profile_display(result.user)
|
||||
_load_match_history()
|
||||
else:
|
||||
_show_error(result.message)
|
||||
|
||||
is_loading = false
|
||||
|
||||
|
||||
func _update_profile_display(user: Dictionary) -> void:
|
||||
username_label.text = user.get("username", "Unknown")
|
||||
|
||||
var stats = user.get("stats", {})
|
||||
elo_value.text = str(stats.get("eloRating", 1000))
|
||||
wins_value.text = str(stats.get("wins", 0))
|
||||
losses_value.text = str(stats.get("losses", 0))
|
||||
games_value.text = str(stats.get("gamesPlayed", 0))
|
||||
|
||||
# Calculate win rate
|
||||
var games_played = stats.get("gamesPlayed", 0)
|
||||
if games_played > 0:
|
||||
var win_rate = float(stats.get("wins", 0)) / float(games_played) * 100.0
|
||||
winrate_value.text = "%.1f%%" % win_rate
|
||||
else:
|
||||
winrate_value.text = "N/A"
|
||||
|
||||
|
||||
func _load_match_history() -> void:
|
||||
loading_label.visible = true
|
||||
_clear_history_list()
|
||||
|
||||
var offset = current_page * MATCHES_PER_PAGE
|
||||
var result = await NetworkManager.get_match_history(MATCHES_PER_PAGE, offset)
|
||||
|
||||
loading_label.visible = false
|
||||
|
||||
if result.success:
|
||||
var matches = result.get("matches", [])
|
||||
matches_cache = matches
|
||||
_display_matches(matches)
|
||||
_update_pagination()
|
||||
else:
|
||||
_show_error(result.message)
|
||||
|
||||
|
||||
func _display_matches(matches: Array) -> void:
|
||||
_clear_history_list()
|
||||
|
||||
if matches.is_empty():
|
||||
var empty_label = Label.new()
|
||||
empty_label.add_theme_font_override("font", custom_font)
|
||||
empty_label.add_theme_font_size_override("font_size", 14)
|
||||
empty_label.add_theme_color_override("font_color", MUTED_COLOR)
|
||||
empty_label.text = "No matches found"
|
||||
empty_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
history_list.add_child(empty_label)
|
||||
return
|
||||
|
||||
for match_data in matches:
|
||||
var match_row = _create_match_row(match_data)
|
||||
history_list.add_child(match_row)
|
||||
|
||||
|
||||
func _create_match_row(match_data: Dictionary) -> HBoxContainer:
|
||||
var row = HBoxContainer.new()
|
||||
row.add_theme_constant_override("separation", 12)
|
||||
|
||||
# Win/Loss indicator
|
||||
var result_label = Label.new()
|
||||
result_label.add_theme_font_override("font", custom_font)
|
||||
result_label.add_theme_font_size_override("font_size", 14)
|
||||
result_label.custom_minimum_size.x = 50
|
||||
|
||||
var is_win = match_data.get("isWin", false)
|
||||
if is_win:
|
||||
result_label.text = "WIN"
|
||||
result_label.add_theme_color_override("font_color", WIN_COLOR)
|
||||
else:
|
||||
result_label.text = "LOSS"
|
||||
result_label.add_theme_color_override("font_color", LOSS_COLOR)
|
||||
row.add_child(result_label)
|
||||
|
||||
# Opponent
|
||||
var vs_label = Label.new()
|
||||
vs_label.add_theme_font_override("font", custom_font)
|
||||
vs_label.add_theme_font_size_override("font_size", 14)
|
||||
vs_label.add_theme_color_override("font_color", TEXT_COLOR)
|
||||
vs_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
|
||||
var opponent = match_data.get("player1", "")
|
||||
var current_username = NetworkManager.current_user.get("username", "")
|
||||
if opponent == current_username:
|
||||
opponent = match_data.get("player2", "Unknown")
|
||||
vs_label.text = "vs %s" % opponent
|
||||
row.add_child(vs_label)
|
||||
|
||||
# Result type
|
||||
var reason_label = Label.new()
|
||||
reason_label.add_theme_font_override("font", custom_font)
|
||||
reason_label.add_theme_font_size_override("font_size", 12)
|
||||
reason_label.add_theme_color_override("font_color", MUTED_COLOR)
|
||||
reason_label.custom_minimum_size.x = 80
|
||||
|
||||
var result_reason = match_data.get("result", "")
|
||||
match result_reason:
|
||||
"damage":
|
||||
reason_label.text = "Damage"
|
||||
"deck_out":
|
||||
reason_label.text = "Deck Out"
|
||||
"concede":
|
||||
reason_label.text = "Concede"
|
||||
"timeout":
|
||||
reason_label.text = "Timeout"
|
||||
"disconnect":
|
||||
reason_label.text = "Disconnect"
|
||||
_:
|
||||
reason_label.text = result_reason.capitalize()
|
||||
row.add_child(reason_label)
|
||||
|
||||
# ELO change
|
||||
var elo_label = Label.new()
|
||||
elo_label.add_theme_font_override("font", custom_font)
|
||||
elo_label.add_theme_font_size_override("font_size", 14)
|
||||
elo_label.custom_minimum_size.x = 60
|
||||
elo_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
|
||||
var elo_change = match_data.get("eloChange", 0)
|
||||
if is_win:
|
||||
elo_label.text = "+%d" % elo_change
|
||||
elo_label.add_theme_color_override("font_color", WIN_COLOR)
|
||||
else:
|
||||
elo_label.text = "-%d" % elo_change
|
||||
elo_label.add_theme_color_override("font_color", LOSS_COLOR)
|
||||
row.add_child(elo_label)
|
||||
|
||||
# Date
|
||||
var date_label = Label.new()
|
||||
date_label.add_theme_font_override("font", custom_font)
|
||||
date_label.add_theme_font_size_override("font_size", 12)
|
||||
date_label.add_theme_color_override("font_color", MUTED_COLOR)
|
||||
date_label.custom_minimum_size.x = 80
|
||||
date_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
|
||||
var played_at = match_data.get("playedAt", "")
|
||||
if played_at != "":
|
||||
# Parse ISO date and format nicely
|
||||
date_label.text = _format_date(played_at)
|
||||
row.add_child(date_label)
|
||||
|
||||
return row
|
||||
|
||||
|
||||
func _format_date(iso_date: String) -> String:
|
||||
# Simple date formatting - extracts date portion
|
||||
if iso_date.contains("T"):
|
||||
var parts = iso_date.split("T")
|
||||
var date_part = parts[0]
|
||||
var date_components = date_part.split("-")
|
||||
if date_components.size() >= 3:
|
||||
return "%s/%s" % [date_components[1], date_components[2]]
|
||||
return iso_date.left(10)
|
||||
|
||||
|
||||
func _clear_history_list() -> void:
|
||||
for child in history_list.get_children():
|
||||
child.queue_free()
|
||||
|
||||
|
||||
func _update_pagination() -> void:
|
||||
page_label.text = "Page %d" % (current_page + 1)
|
||||
prev_button.disabled = current_page == 0
|
||||
# Disable next if we got fewer results than requested (end of data)
|
||||
next_button.disabled = matches_cache.size() < MATCHES_PER_PAGE
|
||||
|
||||
|
||||
func _show_error(message: String) -> void:
|
||||
error_label.text = message
|
||||
error_label.visible = true
|
||||
await get_tree().create_timer(5.0).timeout
|
||||
if is_instance_valid(error_label):
|
||||
error_label.visible = false
|
||||
|
||||
|
||||
func _on_back_pressed() -> void:
|
||||
back_pressed.emit()
|
||||
|
||||
|
||||
func _on_prev_page() -> void:
|
||||
if current_page > 0:
|
||||
current_page -= 1
|
||||
_load_match_history()
|
||||
|
||||
|
||||
func _on_next_page() -> void:
|
||||
current_page += 1
|
||||
_load_match_history()
|
||||
|
||||
|
||||
func refresh() -> void:
|
||||
current_page = 0
|
||||
_load_profile()
|
||||
Reference in New Issue
Block a user