diff --git a/scripts/ui/main.gd b/scripts/ui/main.gd index b5c6669..dbbdc5f 100644 --- a/scripts/ui/main.gd +++ b/scripts/ui/main.gd @@ -11,7 +11,6 @@ var header_title: Label var environment_badge: Label var app_menu: PanelContainer var menu_exit_button: Button -var sound_toggle: CheckButton var theme_button: Button var feedback_button: Button var feedback_dialog: Control @@ -855,7 +854,7 @@ func _build_app_menu() -> void: 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.custom_minimum_size = Vector2(340, 290) app_menu.add_theme_stylebox_override("panel", Style.panel_box(Color("172337"), Style.CYAN)) add_child(app_menu) @@ -877,13 +876,21 @@ func _build_app_menu() -> void: 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 := Button.new() + profile_button.text = "Profile" + profile_button.custom_minimum_size = Vector2(0, 48) + Style.style_chip(profile_button, Style.CYAN) + UISoundService.set_button_sound(profile_button, "open") + profile_button.pressed.connect(_navigate_from_pause.bind("profile")) + content.add_child(profile_button) + + var settings_button := Button.new() + settings_button.text = "Settings" + settings_button.custom_minimum_size = Vector2(0, 48) + Style.style_chip(settings_button, Style.CYAN) + UISoundService.set_button_sound(settings_button, "open") + settings_button.pressed.connect(_navigate_from_pause.bind("settings")) + content.add_child(settings_button) menu_exit_button = Button.new() menu_exit_button.text = "Exit Run" @@ -929,6 +936,13 @@ func _close_app_menu() -> void: BlockSessionService.resume_current_question_timer() +func _navigate_from_pause(target_screen: String) -> void: + var return_screen := current_screen_name + show_screen(target_screen, {"return_screen": return_screen}) + if return_screen == "player": + BlockSessionService.pause_current_question_timer() + + func _exit_run() -> void: _close_app_menu() if current_screen_name == "ccs_player": diff --git a/scripts/ui/progress_profile.gd b/scripts/ui/progress_profile.gd index 971fb05..43c285e 100644 --- a/scripts/ui/progress_profile.gd +++ b/scripts/ui/progress_profile.gd @@ -19,6 +19,8 @@ var outcome_consent: CheckBox var outcome_status: Label var outcome_delete: Button var connected_accounts_list: VBoxContainer +var return_screen := "" +var back_to_session_button: Button func _ready() -> void: @@ -38,7 +40,10 @@ func _ready() -> void: _build() -func on_show(_payload: Variant = null) -> void: +func on_show(payload: Variant = null) -> void: + var wrapper: Dictionary = payload if typeof(payload) == TYPE_DICTIONARY else {} + return_screen = str(wrapper.get("return_screen", "")) + _refresh_back_to_session_visibility() _render() AccountProfileService.refresh() DashboardService.refresh() @@ -63,6 +68,11 @@ func _build() -> void: status_label = _label("", 13, Brand.muted_color(_dark())) status_label.visible = false page.add_child(status_label) + back_to_session_button = _button("Back to session", "", true) + back_to_session_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN + UISoundService.set_button_sound(back_to_session_button, "back") + back_to_session_button.pressed.connect(_return_to_session) + page.add_child(back_to_session_button) columns = GridContainer.new() columns.columns = 2 columns.size_flags_horizontal = Control.SIZE_EXPAND_FILL @@ -87,6 +97,7 @@ func _build() -> void: right.add_child(_outcome_card()) right.add_child(_connected_accounts_card()) right.add_child(_account_actions_card()) + _refresh_back_to_session_visibility() _update_responsive() _render() @@ -510,6 +521,17 @@ func _button(text: String, icon_name: String = "", primary: bool = false) -> But return button +func _refresh_back_to_session_visibility() -> void: + if back_to_session_button != null: + back_to_session_button.visible = not return_screen.is_empty() + + +func _return_to_session() -> void: + if return_screen.is_empty(): + return + main.show_screen(return_screen) + + func _action_button(text: String, icon_name: String, color: Color = Color.TRANSPARENT) -> Button: var button := _button(text, icon_name, false) button.alignment = HORIZONTAL_ALIGNMENT_LEFT diff --git a/scripts/ui/settings_screen.gd b/scripts/ui/settings_screen.gd index 970ed1a..335d634 100644 --- a/scripts/ui/settings_screen.gd +++ b/scripts/ui/settings_screen.gd @@ -13,6 +13,8 @@ var followups_toggle: CheckButton var question_scale_value_label: Label var ccs_scale_value_label: Label var core_block_size_value_label: Label +var return_screen := "" +var back_to_session_button: Button func _ready() -> void: @@ -39,6 +41,17 @@ func _ready() -> void: subtitle.add_theme_color_override("font_color", Brand.SLATE) add_child(subtitle) + back_to_session_button = Button.new() + back_to_session_button.text = "Back to session" + back_to_session_button.custom_minimum_size = Vector2(0, 44) + back_to_session_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN + back_to_session_button.add_theme_stylebox_override("normal", Brand.gradient_button_box()) + back_to_session_button.add_theme_color_override("font_color", Color.WHITE) + back_to_session_button.visible = false + UISoundService.set_button_sound(back_to_session_button, "back") + back_to_session_button.pressed.connect(_return_to_session) + add_child(back_to_session_button) + var panel := PanelContainer.new() panel.custom_minimum_size = Vector2(520, 0) panel.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN @@ -98,7 +111,10 @@ func _ready() -> void: _refresh_text_scale_labels() -func on_show(_payload: Variant = null) -> void: +func on_show(payload: Variant = null) -> void: + var wrapper: Dictionary = payload if typeof(payload) == TYPE_DICTIONARY else {} + return_screen = str(wrapper.get("return_screen", "")) + back_to_session_button.visible = not return_screen.is_empty() theme_toggle.set_pressed_no_signal(UIThemeService.is_dark_mode()) sound_toggle.set_pressed_no_signal(UISoundService.are_sounds_enabled()) followups_toggle.set_pressed_no_signal(BlockSessionService.show_review_followups) @@ -209,6 +225,12 @@ func _open_legal_document(kind: String) -> void: main.open_legal_document(kind) +func _return_to_session() -> void: + if return_screen.is_empty(): + return + main.show_screen(return_screen) + + func _on_theme_changed(is_dark: bool) -> void: theme_toggle.set_pressed_no_signal(is_dark) var panel := theme_toggle.get_parent().get_parent() as PanelContainer diff --git a/tests/web_responsive_test.gd b/tests/web_responsive_test.gd index 22e4065..2d3906f 100644 --- a/tests/web_responsive_test.gd +++ b/tests/web_responsive_test.gd @@ -50,10 +50,13 @@ func _run() -> void: _assert(main_screen.account_chevron_button.get_index() == main_screen.account_button.get_index() + 1, "the account avatar and chevron should remain adjacent") _assert(main_screen.header_panel.find_child("HeaderStats", true, false) == null, "the application header should not display rating or role labels") var pause_menu_has_profile := false + var pause_menu_has_settings := false for menu_button in main_screen.app_menu.find_children("*", "Button", true, false): if menu_button.text == "Profile": pause_menu_has_profile = true - _assert(not pause_menu_has_profile, "the paused-run menu should not duplicate profile navigation") + if menu_button.text == "Settings": + pause_menu_has_settings = true + _assert(pause_menu_has_profile and pause_menu_has_settings, "the paused-run menu should offer Profile and Settings navigation") _assert(main_screen.session_expiry_dialog != null, "the application shell should construct a global session-expiry prompt") main_screen.session_expiry_dialog._show_warning(120) _assert(main_screen.session_expiry_dialog.visible, "the session-expiry prompt should cover every authenticated screen")