diff --git a/scripts/autoload/block_session_service.gd b/scripts/autoload/block_session_service.gd index a2a2fd8..e582bce 100644 --- a/scripts/autoload/block_session_service.gd +++ b/scripts/autoload/block_session_service.gd @@ -28,10 +28,41 @@ var simulation_block_index := 0 var simulation_total_blocks := 12 var simulation_deadline_unix := 0.0 var review_first_submission_recorded := false +var core_block_size := DEFAULT_CORE_BLOCK_SIZE const CORE_MIN_TARGET_SECONDS := 45.0 const CORE_MAX_TARGET_SECONDS := 150.0 const CORE_RETRY_TARGET_SECONDS := 30.0 +const DEFAULT_CORE_BLOCK_SIZE := 10 +const CORE_BLOCK_SIZE_MIN := 5 +const CORE_BLOCK_SIZE_MAX := 40 +const SETTINGS_PATH := "user://settings.cfg" +const SETTINGS_SECTION := "session" +const SETTINGS_KEY_CORE_BLOCK_SIZE := "core_block_size" + + +func _ready() -> void: + _load_core_block_size() + + +func set_core_block_size(value: int) -> void: + var clamped := clampi(value, CORE_BLOCK_SIZE_MIN, CORE_BLOCK_SIZE_MAX) + if clamped == core_block_size: + return + core_block_size = clamped + var config := ConfigFile.new() + config.load(SETTINGS_PATH) + config.set_value(SETTINGS_SECTION, SETTINGS_KEY_CORE_BLOCK_SIZE, core_block_size) + if config.save(SETTINGS_PATH) != OK: + push_warning("Unable to save core question block size preference.") + + +func _load_core_block_size() -> void: + var config := ConfigFile.new() + if config.load(SETTINGS_PATH) == OK: + core_block_size = clampi( + int(config.get_value(SETTINGS_SECTION, SETTINGS_KEY_CORE_BLOCK_SIZE, DEFAULT_CORE_BLOCK_SIZE)), + CORE_BLOCK_SIZE_MIN, CORE_BLOCK_SIZE_MAX) func start_dynamic_question(question: Dictionary) -> bool: diff --git a/scripts/ui/question_player.gd b/scripts/ui/question_player.gd index 28c4043..3604e1b 100644 --- a/scripts/ui/question_player.gd +++ b/scripts/ui/question_player.gd @@ -221,13 +221,10 @@ func _ready() -> void: header_stack = VBoxContainer.new() header_stack.add_theme_constant_override("separation", 8) card_content.add_child(header_stack) - header_row = HBoxContainer.new() - header_row.add_theme_constant_override("separation", 16) - header_stack.add_child(header_row) var header_brand := HBoxContainer.new() header_brand.add_theme_constant_override("separation", 8) - header_row.add_child(header_brand) + header_stack.add_child(header_brand) var header_brand_mark := TextureRect.new() header_brand_mark.texture = Brand.ranaze_mark_texture() header_brand_mark.custom_minimum_size = Vector2(28, 28) @@ -240,6 +237,10 @@ func _ready() -> void: header_wordmark_label.add_theme_font_size_override("font_size", 18) header_brand.add_child(header_wordmark_label) + header_row = HBoxContainer.new() + header_row.add_theme_constant_override("separation", 16) + header_stack.add_child(header_row) + var progress_group := VBoxContainer.new() progress_group.add_theme_constant_override("separation", 2) progress_group.size_flags_vertical = Control.SIZE_SHRINK_CENTER @@ -660,15 +661,13 @@ func _ready() -> void: bottom_progress_bar.size_flags_horizontal = Control.SIZE_SHRINK_CENTER bottom_progress_group.add_child(bottom_progress_bar) - var time_group := VBoxContainer.new() - time_group.add_theme_constant_override("separation", 0) - actions.add_child(time_group) - + # The timer readout still runs (BlockSessionService pacing/scoring depends on + # it, and _update_timer_bar keeps its text current), it's just no longer + # parented into the bottom bar - the caption + digits and the "Ctrl+Enter" + # hint were clutter next to the Submit button. timer_caption_label = Label.new() timer_caption_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT _set_scaled_label(timer_caption_label, 12, Brand.SLATE) - time_group.add_child(timer_caption_label) - time_group.add_child(timer_label) next_button = Button.new() next_button.text = "Submit Block" if BlockSessionService.session_kind == "simulation" and BlockSessionService.current_index + 1 == BlockSessionService.active_questions.size() else "Next" if BlockSessionService.session_kind == "simulation" else "Submit" @@ -678,12 +677,6 @@ func _ready() -> void: next_button.pressed.connect(_submit_or_next) actions.add_child(next_button) - var shortcut_hint_label := Label.new() - shortcut_hint_label.name = "SubmitShortcutHint" - shortcut_hint_label.text = "⌘ Enter" if OS.get_name() == "macOS" else "Ctrl+Enter" - _set_scaled_label(shortcut_hint_label, 11, Brand.SLATE) - actions.add_child(shortcut_hint_label) - timer_footer = HBoxContainer.new() timer_footer.add_theme_constant_override("separation", 12) card_content.add_child(timer_footer) @@ -793,7 +786,9 @@ func _refresh_responsive_layout() -> void: header_toolbar_host.reparent(target_parent) header_toolbar_host.size_flags_horizontal = Control.SIZE_EXPAND_FILL if compact else Control.SIZE_SHRINK_END question_toolbar.size_flags_horizontal = Control.SIZE_EXPAND_FILL - header_toolbar_host.custom_minimum_size.x = 0.0 if compact else 520.0 + # 11 bare 44px icon buttons + the wider 92px Feedback button, with 8px gaps + # between all 12 - anything less than this wraps onto a second row. + header_toolbar_host.custom_minimum_size.x = 0.0 if compact else 680.0 if size.x < 560.0: nav_palette.columns = 5 else: @@ -1074,9 +1069,6 @@ func _apply_question_theme() -> void: skip_button.add_theme_color_override("font_color", skip_link_color) skip_button.add_theme_color_override("font_hover_color", skip_link_color) skip_button.add_theme_color_override("font_disabled_color", Brand.muted_color(dark)) - var shortcut_hint := find_child("SubmitShortcutHint", true, false) as Label - if shortcut_hint != null: - shortcut_hint.add_theme_color_override("font_color", Brand.muted_color(dark)) if next_button != null: next_button.add_theme_stylebox_override("normal", _primary_action_box(dark, "normal")) next_button.add_theme_stylebox_override("hover", _primary_action_box(dark, "hover")) @@ -2078,8 +2070,9 @@ func _is_session_timer_visible() -> bool: func _question_progress_text() -> String: if BlockSessionService.session_kind == "core": - return "Question %d" % (BlockSessionService.core_answer_count + 1) - return "Question %d of %d" % [ + var block_size := BlockSessionService.core_block_size + return "%d of %d" % [(BlockSessionService.core_answer_count % block_size) + 1, block_size] + return "%d of %d" % [ mini(BlockSessionService.current_index + 1, BlockSessionService.active_questions.size()), BlockSessionService.active_questions.size() ] diff --git a/scripts/ui/settings_screen.gd b/scripts/ui/settings_screen.gd index 7de9f93..9932d00 100644 --- a/scripts/ui/settings_screen.gd +++ b/scripts/ui/settings_screen.gd @@ -4,12 +4,14 @@ const Brand := preload("res://scripts/ui/medmind_style.gd") const QUESTION_TEXT_SCALE_MIN := 0.85 const QUESTION_TEXT_SCALE_MAX := 1.35 const QUESTION_TEXT_SCALE_STEP := 0.1 +const CORE_BLOCK_SIZE_STEP := 5 var main: Control var theme_toggle: CheckButton var sound_toggle: CheckButton var question_scale_value_label: Label var ccs_scale_value_label: Label +var core_block_size_value_label: Label func _ready() -> void: @@ -59,6 +61,7 @@ func _ready() -> void: content.add_child(_build_text_scale_row("Question bank text size", "question")) content.add_child(_build_text_scale_row("CCS case text size", "ccs")) + content.add_child(_build_core_block_size_row()) var legal_panel := PanelContainer.new() legal_panel.custom_minimum_size = Vector2(520, 0) @@ -131,6 +134,42 @@ func _build_text_scale_row(label_text: String, kind: String) -> HBoxContainer: return row +func _build_core_block_size_row() -> HBoxContainer: + var row := HBoxContainer.new() + row.custom_minimum_size = Vector2(0, 52) + row.add_theme_constant_override("separation", 12) + + var label := Label.new() + label.text = "Question block size" + label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + row.add_child(label) + + var smaller := Button.new() + smaller.text = "-" + smaller.tooltip_text = "Fewer questions per block" + smaller.custom_minimum_size = Vector2(44, 44) + smaller.pressed.connect(_adjust_core_block_size.bind(-CORE_BLOCK_SIZE_STEP)) + row.add_child(smaller) + + core_block_size_value_label = Label.new() + core_block_size_value_label.custom_minimum_size = Vector2(56, 0) + core_block_size_value_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + row.add_child(core_block_size_value_label) + + var larger := Button.new() + larger.text = "+" + larger.tooltip_text = "More questions per block" + larger.custom_minimum_size = Vector2(44, 44) + larger.pressed.connect(_adjust_core_block_size.bind(CORE_BLOCK_SIZE_STEP)) + row.add_child(larger) + return row + + +func _adjust_core_block_size(delta: int) -> void: + BlockSessionService.set_core_block_size(BlockSessionService.core_block_size + delta) + _refresh_text_scale_labels() + + func _adjust_question_text_scale(delta: float) -> void: var current: float = AccountProfileService.preferences.get("questionTextScale", 1.0) var next_scale: float = clamp(current + delta, QUESTION_TEXT_SCALE_MIN, QUESTION_TEXT_SCALE_MAX) @@ -149,6 +188,8 @@ func _refresh_text_scale_labels() -> void: question_scale_value_label.text = "%d%%" % round(question_scale * 100) if ccs_scale_value_label != null: ccs_scale_value_label.text = "%d%%" % round(CCSService.text_scale * 100) + if core_block_size_value_label != null: + core_block_size_value_label.text = str(BlockSessionService.core_block_size) func _on_theme_toggled(enabled: bool) -> void: