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 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: BlockSessionService.finish_block() 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: 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: BlockSessionService.finish_block() show_screen("selection", "step3") func _open_profile_from_menu() -> void: _close_app_menu() if BlockSessionService.block_started: BlockSessionService.finish_block() 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")