Files
step3/scripts/ui/main.gd
T

351 lines
12 KiB
GDScript

extends Control
const Style := preload("res://scripts/ui/ui_style.gd")
var screens := {}
var header_stats: Label
var header_panel: PanelContainer
var user_label: Label
var app_menu: PanelContainer
var menu_exit_button: Button
var sound_toggle: CheckButton
var theme_button: Button
var theme_service
var current_screen_name := ""
func _ready() -> void:
theme_service = get_node("/root/UIThemeService")
Style.apply_app_theme(self)
_build_ui()
theme_service.theme_changed.connect(_on_theme_changed)
resized.connect(_update_responsive_layout)
_update_responsive_layout()
show_screen("selection" if AuthState.is_logged_in else "login", "exam")
func _update_responsive_layout() -> void:
if header_stats != null:
header_stats.visible = size.x >= 900.0
if app_menu != null:
app_menu.custom_minimum_size.x = minf(340.0, maxf(280.0, size.x - 32.0))
if app_menu.visible:
app_menu.size = app_menu.custom_minimum_size
app_menu.position = (get_viewport_rect().size - app_menu.size) * 0.5
func show_screen(screen_name: String, payload: Variant = null) -> void:
_close_app_menu()
for child in $Root/Screens.get_children():
if child.visible and child.has_method("on_hide"):
child.on_hide()
child.visible = false
var screen = screens.get(screen_name)
if screen == null:
return
current_screen_name = screen_name
screen.visible = true
if header_panel != null:
header_panel.visible = screen_name != "login" and screen_name != "player"
if screen.has_method("on_show"):
screen.on_show(payload)
func _build_ui() -> void:
var background := ColorRect.new()
background.color = Style.BACKGROUND
background.set_anchors_preset(Control.PRESET_FULL_RECT)
add_child(background)
var root := VBoxContainer.new()
root.name = "Root"
root.set_anchors_preset(Control.PRESET_FULL_RECT)
root.add_theme_constant_override("separation", 0)
add_child(root)
header_panel = PanelContainer.new()
header_panel.name = "Header"
header_panel.add_theme_stylebox_override("panel", Style.panel_box(Style.SURFACE_DARK, Color("263752")))
root.add_child(header_panel)
var header := HBoxContainer.new()
header.custom_minimum_size = Vector2(0, 78)
header.add_theme_constant_override("separation", 14)
header_panel.add_child(header)
var title := Label.new()
title.text = "RanaZe's Doctor Maker"
Style.set_label(title, 31, Style.TEXT)
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header.add_child(title)
header_stats = Label.new()
header_stats.name = "HeaderStats"
Style.set_label(header_stats, 15, Style.MUTED)
header_stats.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header_stats.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
header.add_child(header_stats)
user_label = Label.new()
Style.set_label(user_label, 15, Style.CYAN)
user_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
user_label.mouse_filter = Control.MOUSE_FILTER_STOP
user_label.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
user_label.tooltip_text = "Open profile"
user_label.gui_input.connect(_on_user_label_gui_input)
header.add_child(user_label)
theme_button = Button.new()
theme_button.custom_minimum_size = Vector2(44, 44)
theme_button.add_theme_font_size_override("font_size", 22)
UISoundService.set_button_sound(theme_button, "toggle")
theme_button.pressed.connect(theme_service.toggle_theme)
header.add_child(theme_button)
_refresh_theme_button()
var screens_container := MarginContainer.new()
screens_container.name = "Screens"
screens_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
screens_container.add_theme_constant_override("margin_left", 24)
screens_container.add_theme_constant_override("margin_right", 24)
screens_container.add_theme_constant_override("margin_top", 18)
screens_container.add_theme_constant_override("margin_bottom", 22)
root.add_child(screens_container)
var menu := preload("res://scripts/ui/main_menu.gd").new()
menu.name = "MainMenu"
menu.main = self
screens_container.add_child(menu)
screens["menu"] = menu
var login := preload("res://scripts/ui/login_screen.gd").new()
login.name = "LoginScreen"
login.main = self
screens_container.add_child(login)
screens["login"] = login
var selector := preload("res://scripts/ui/study_selector.gd").new()
selector.name = "StudySelector"
selector.main = self
screens_container.add_child(selector)
screens["selection"] = selector
var map := preload("res://scripts/ui/study_map.gd").new()
map.name = "StudyMap"
map.main = self
screens_container.add_child(map)
screens["map"] = map
var setup := preload("res://scripts/ui/block_setup.gd").new()
setup.name = "BlockSetup"
setup.main = self
screens_container.add_child(setup)
screens["setup"] = setup
var player := preload("res://scripts/ui/question_player.gd").new()
player.name = "QuestionPlayer"
player.main = self
screens_container.add_child(player)
screens["player"] = player
var review := preload("res://scripts/ui/review_results.gd").new()
review.name = "ReviewResults"
review.main = self
screens_container.add_child(review)
screens["review"] = review
var simulation_break := preload("res://scripts/ui/simulation_break.gd").new()
simulation_break.name = "SimulationBreak"
simulation_break.main = self
screens_container.add_child(simulation_break)
screens["simulation_break"] = simulation_break
var simulation_results := preload("res://scripts/ui/simulation_results.gd").new()
simulation_results.name = "SimulationResults"
simulation_results.main = self
screens_container.add_child(simulation_results)
screens["simulation_results"] = simulation_results
var simulation_review := preload("res://scripts/ui/simulation_review.gd").new()
simulation_review.name = "SimulationReview"
simulation_review.main = self
screens_container.add_child(simulation_review)
screens["simulation_review"] = simulation_review
var simulation_history := preload("res://scripts/ui/simulation_history.gd").new()
simulation_history.name = "SimulationHistory"
simulation_history.main = self
screens_container.add_child(simulation_history)
screens["simulation_history"] = simulation_history
var profile := preload("res://scripts/ui/progress_profile.gd").new()
profile.name = "ProgressProfile"
profile.main = self
screens_container.add_child(profile)
screens["profile"] = profile
ProfileService.profile_changed.connect(_refresh_header_stats)
QuestionBankService.ratings_changed.connect(_refresh_header_stats)
AuthState.auth_changed.connect(_refresh_header_stats)
_refresh_header_stats()
_build_app_menu()
func _input(event: InputEvent) -> void:
if not AuthState.is_logged_in or current_screen_name == "login":
return
if event.is_action_pressed("ui_cancel"):
_toggle_app_menu()
get_viewport().set_input_as_handled()
func _refresh_header_stats() -> void:
if header_stats == null:
return
var profile := ProfileService.get_profile()
header_stats.text = "Rating: %d | Streak %d" % [
QuestionBankService.overall_elo,
profile.get("current_streak", 0)
]
if user_label != null:
user_label.text = AuthState.username if AuthState.is_logged_in else ""
func _on_theme_changed(is_dark_mode: bool) -> void:
Style.refresh_tree(self, not is_dark_mode)
_refresh_theme_button()
var player = screens.get("player")
if player != null and player.has_method("refresh_theme_controls"):
player.refresh_theme_controls()
func _refresh_theme_button() -> void:
if theme_button == null:
return
theme_button.text = "☀" if theme_service.is_dark_mode() else "☾"
theme_button.tooltip_text = "Switch to light mode" if theme_service.is_dark_mode() else "Switch to dark mode"
Style.style_chip(theme_button, Style.CYAN)
func _logout() -> void:
_close_app_menu()
if BlockSessionService.block_started and BlockSessionService.session_kind != "simulation":
BlockSessionService.finish_block()
elif BlockSessionService.session_kind == "simulation":
BlockSessionService.block_started = false
QuestionBankService.clear_questions()
AuthState.clear_session()
show_screen("login")
func _build_app_menu() -> void:
app_menu = PanelContainer.new()
app_menu.visible = false
app_menu.top_level = true
app_menu.mouse_filter = Control.MOUSE_FILTER_STOP
app_menu.custom_minimum_size = Vector2(340, 230)
app_menu.add_theme_stylebox_override("panel", Style.panel_box(Color("172337"), Style.CYAN))
add_child(app_menu)
var content := VBoxContainer.new()
content.add_theme_constant_override("separation", 12)
app_menu.add_child(content)
var title := Label.new()
title.text = "Paused"
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
Style.set_label(title, 28, Style.TEXT)
content.add_child(title)
var resume := Button.new()
resume.text = "Resume"
resume.custom_minimum_size = Vector2(0, 48)
Style.style_primary_button(resume)
UISoundService.set_button_sound(resume, "close")
resume.pressed.connect(_close_app_menu)
content.add_child(resume)
sound_toggle = CheckButton.new()
sound_toggle.text = "UI Sounds"
sound_toggle.button_pressed = UISoundService.are_sounds_enabled()
sound_toggle.custom_minimum_size = Vector2(0, 44)
UISoundService.set_button_sound(sound_toggle, "none")
sound_toggle.toggled.connect(UISoundService.set_sounds_enabled)
content.add_child(sound_toggle)
var profile := Button.new()
profile.text = "Profile"
profile.custom_minimum_size = Vector2(0, 48)
Style.style_chip(profile, Style.CYAN)
profile.pressed.connect(_open_profile_from_menu)
content.add_child(profile)
menu_exit_button = Button.new()
menu_exit_button.text = "Exit Run"
menu_exit_button.custom_minimum_size = Vector2(0, 48)
UISoundService.set_button_sound(menu_exit_button, "back")
menu_exit_button.pressed.connect(_exit_run)
content.add_child(menu_exit_button)
var logout := Button.new()
logout.text = "Log Out"
logout.custom_minimum_size = Vector2(0, 48)
Style.style_chip(logout, Style.RED)
UISoundService.set_button_sound(logout, "back")
logout.pressed.connect(_logout)
content.add_child(logout)
func _toggle_app_menu() -> void:
if app_menu == null:
return
if app_menu.visible:
UISoundService.play_sound("close")
_close_app_menu()
else:
UISoundService.play_sound("open")
app_menu.visible = true
app_menu.size = app_menu.custom_minimum_size
app_menu.position = (get_viewport_rect().size - app_menu.size) * 0.5
menu_exit_button.visible = current_screen_name == "player" and BlockSessionService.block_started
if menu_exit_button.visible:
menu_exit_button.text = "Leave (timer continues)" if BlockSessionService.session_kind == "simulation" else "Exit Run"
Style.style_chip(menu_exit_button, Style.AMBER)
BlockSessionService.pause_current_question_timer()
func _close_app_menu() -> void:
if app_menu != null:
app_menu.visible = false
BlockSessionService.resume_current_question_timer()
func _exit_run() -> void:
_close_app_menu()
if BlockSessionService.block_started and BlockSessionService.session_kind != "simulation":
BlockSessionService.finish_block()
elif BlockSessionService.session_kind == "simulation":
BlockSessionService.block_started = false
show_screen("selection", "step3")
func _open_profile_from_menu() -> void:
_close_app_menu()
if BlockSessionService.block_started and BlockSessionService.session_kind != "simulation":
BlockSessionService.finish_block()
elif BlockSessionService.session_kind == "simulation":
BlockSessionService.block_started = false
show_screen("profile")
func _on_user_label_gui_input(event: InputEvent) -> void:
if not AuthState.is_logged_in:
return
var mouse_event := event as InputEventMouseButton
if mouse_event == null or mouse_event.button_index != MOUSE_BUTTON_LEFT or not mouse_event.pressed:
return
_close_app_menu()
if current_screen_name != "profile":
show_screen("profile")