Files
step3/scripts/ui/study_selector.gd
T

247 lines
8.6 KiB
GDScript

extends VBoxContainer
const Style := preload("res://scripts/ui/ui_style.gd")
var main: Control
var exam_view: VBoxContainer
var mode_view: VBoxContainer
var exam_grid: GridContainer
var mode_grid: GridContainer
var question_bank_button: Button
var bank_status_label: Label
var bank_error_label: Label
var retry_button: Button
var current_stage := "exam"
var pending_question_bank_launch := false
func _ready() -> void:
set_anchors_preset(Control.PRESET_FULL_RECT)
add_theme_constant_override("separation", 18)
resized.connect(_refresh_grid_columns)
if not QuestionBankService.questions_changed.is_connected(_on_questions_changed):
QuestionBankService.questions_changed.connect(_on_questions_changed)
if not QuestionBankService.questions_load_failed.is_connected(_on_questions_load_failed):
QuestionBankService.questions_load_failed.connect(_on_questions_load_failed)
_build_exam_view()
_build_mode_view()
_show_stage("exam")
func on_show(payload: Variant = null) -> void:
if str(payload) == "step3":
_show_stage("step3")
elif str(payload) == "exam":
_show_stage("exam")
_refresh_bank_state()
func on_hide() -> void:
pending_question_bank_launch = false
func _build_exam_view() -> void:
exam_view = VBoxContainer.new()
exam_view.add_theme_constant_override("separation", 18)
exam_view.size_flags_vertical = Control.SIZE_EXPAND_FILL
add_child(exam_view)
var eyebrow := Label.new()
eyebrow.text = "CHOOSE YOUR EXAM"
Style.set_label(eyebrow, 14, Style.CYAN)
exam_view.add_child(eyebrow)
var title := Label.new()
title.text = "What are you preparing for?"
Style.set_label(title, 34, Style.TEXT)
exam_view.add_child(title)
var subtitle := Label.new()
subtitle.text = "Select a path to enter its study modes. More exams will be added as they are ready."
subtitle.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
Style.set_label(subtitle, 17, Style.MUTED)
exam_view.add_child(subtitle)
exam_grid = GridContainer.new()
exam_grid.columns = 2
exam_grid.add_theme_constant_override("h_separation", 16)
exam_grid.add_theme_constant_override("v_separation", 16)
exam_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
exam_grid.size_flags_vertical = Control.SIZE_EXPAND_FILL
exam_view.add_child(exam_grid)
_add_exam_option("STEP 1", "Foundational sciences", false)
_add_exam_option("STEP 2", "Clinical knowledge", false)
_add_exam_option("STEP 3", "Advanced clinical management", true)
_add_exam_option("CCS Cases", "Interactive case simulations", false)
func _build_mode_view() -> void:
mode_view = VBoxContainer.new()
mode_view.add_theme_constant_override("separation", 18)
mode_view.size_flags_vertical = Control.SIZE_EXPAND_FILL
add_child(mode_view)
var back_button := Button.new()
back_button.text = "< All exams"
back_button.alignment = HORIZONTAL_ALIGNMENT_LEFT
back_button.custom_minimum_size = Vector2(150, 42)
Style.style_chip(back_button, Style.CYAN)
UISoundService.set_button_sound(back_button, "back")
back_button.pressed.connect(func(): _show_stage("exam"))
mode_view.add_child(back_button)
var eyebrow := Label.new()
eyebrow.text = "STEP 3"
Style.set_label(eyebrow, 14, Style.LIME)
mode_view.add_child(eyebrow)
var title := Label.new()
title.text = "Choose a study mode"
Style.set_label(title, 34, Style.TEXT)
mode_view.add_child(title)
var subtitle := Label.new()
subtitle.text = "Build clinical judgment through focused questions. Additional modes are in development."
subtitle.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
Style.set_label(subtitle, 17, Style.MUTED)
mode_view.add_child(subtitle)
mode_grid = GridContainer.new()
mode_grid.columns = 2
mode_grid.add_theme_constant_override("h_separation", 16)
mode_grid.add_theme_constant_override("v_separation", 16)
mode_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
mode_view.add_child(mode_grid)
question_bank_button = Button.new()
question_bank_button.text = "Question Bank\nAdaptive core run | Available"
question_bank_button.alignment = HORIZONTAL_ALIGNMENT_LEFT
question_bank_button.custom_minimum_size = Vector2(360, 150)
question_bank_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
question_bank_button.add_theme_font_size_override("font_size", 20)
Style.style_primary_button(question_bank_button)
question_bank_button.pressed.connect(_select_question_bank)
mode_grid.add_child(question_bank_button)
var dynamic_button := Button.new()
dynamic_button.text = "Dynamic Questions\nAI-generated practice | Coming Soon"
dynamic_button.alignment = HORIZONTAL_ALIGNMENT_LEFT
dynamic_button.custom_minimum_size = Vector2(360, 150)
dynamic_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
dynamic_button.add_theme_font_size_override("font_size", 20)
dynamic_button.disabled = true
mode_grid.add_child(dynamic_button)
bank_status_label = Label.new()
bank_status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
Style.set_label(bank_status_label, 16, Style.CYAN)
mode_view.add_child(bank_status_label)
bank_error_label = Label.new()
bank_error_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
Style.set_label(bank_error_label, 15, Style.RED)
mode_view.add_child(bank_error_label)
retry_button = Button.new()
retry_button.text = "Retry Question Bank"
retry_button.custom_minimum_size = Vector2(220, 46)
retry_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
Style.style_chip(retry_button, Style.AMBER)
retry_button.pressed.connect(_retry_question_bank)
mode_view.add_child(retry_button)
var spacer := Control.new()
spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL
mode_view.add_child(spacer)
func _add_exam_option(option_name: String, description: String, available: bool) -> void:
var button := Button.new()
button.text = "%s\n%s | %s" % [option_name, description, "Available" if available else "Coming Soon"]
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
button.custom_minimum_size = Vector2(360, 150)
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
button.size_flags_vertical = Control.SIZE_EXPAND_FILL
button.add_theme_font_size_override("font_size", 20)
button.disabled = not available
if available:
Style.style_primary_button(button)
button.pressed.connect(func(): _show_stage("step3"))
exam_grid.add_child(button)
func _show_stage(stage_name: String) -> void:
current_stage = stage_name
pending_question_bank_launch = false
if exam_view != null:
exam_view.visible = stage_name == "exam"
if mode_view != null:
mode_view.visible = stage_name == "step3"
_refresh_grid_columns()
_refresh_bank_state()
func _select_question_bank() -> void:
pending_question_bank_launch = true
var excluded_ids: Array = ProfileService.get_profile().get("correct_question_ids", []).duplicate()
QuestionBankService.load_next_random_question(excluded_ids)
_refresh_bank_state()
func _retry_question_bank() -> void:
pending_question_bank_launch = true
QuestionBankService.retry_now()
_refresh_bank_state()
func _on_questions_changed() -> void:
_refresh_bank_state()
if pending_question_bank_launch and visible and current_stage == "step3" and QuestionBankService.load_state == "ready":
pending_question_bank_launch = false
call_deferred("_start_question_bank")
func _on_questions_load_failed(_message: String) -> void:
pending_question_bank_launch = false
_refresh_bank_state()
func _refresh_bank_state() -> void:
if question_bank_button == null:
return
var state := QuestionBankService.load_state
question_bank_button.disabled = pending_question_bank_launch
retry_button.visible = state == "failed" or state == "retrying"
retry_button.disabled = state == "retrying"
bank_error_label.text = QuestionBankService.last_error if state == "failed" or state == "retrying" else ""
if pending_question_bank_launch or state == "loading":
bank_status_label.text = "Preparing your question bank..."
elif state == "retrying":
bank_status_label.text = "Question bank unavailable. Retrying in %d seconds." % QuestionBankService.next_retry_seconds
elif state == "ready":
bank_status_label.text = "Question bank ready"
elif state == "failed":
bank_status_label.text = "The question bank could not be loaded."
else:
bank_status_label.text = "Question bank will load when selected."
func _start_question_bank() -> void:
if not visible or current_stage != "step3" or not QuestionBankService.has_questions():
return
if BlockSessionService.start_core_run():
UISoundService.play_sound("question")
main.show_screen("player")
else:
bank_error_label.text = BlockSessionService.core_status_message
func _refresh_grid_columns() -> void:
var column_count := 1 if size.x < 820.0 else 2
if exam_grid != null:
exam_grid.columns = column_count
if mode_grid != null:
mode_grid.columns = column_count