Add study selection and account registration flows

This commit is contained in:
hz
2026-07-11 12:39:10 -05:00
parent 0f9bd2fa78
commit 8bd7f5e4e8
8 changed files with 749 additions and 282 deletions
+245 -55
View File
@@ -3,6 +3,9 @@ extends VBoxContainer
const Style := preload("res://scripts/ui/ui_style.gd")
const FEEDBACK_FADE_SECONDS := 0.45
const CORRECT_ADVANCE_DELAY_SECONDS := 0.6
const TEXT_SCALE_MIN := 0.85
const TEXT_SCALE_MAX := 1.35
const TEXT_SCALE_STEP := 0.1
var main: Control
var choice_buttons: Array[Button] = []
@@ -25,18 +28,24 @@ var stem_plain_text := ""
var highlight_drag_started_in_stem := false
var timer_color_state := ""
var pending_correct_advance := false
var question_text_scale := 1.0
var presented_choices: Array = []
var progress_label: Label
var timer_label: Label
var player_stats_label: Label
var stem_label: RichTextLabel
var lead_label: Label
var explanation_label: RichTextLabel
var question_elo_label: Label
var next_button: Button
var skip_button: Button
var text_smaller_button: Button
var text_larger_button: Button
var labs_button: Button
var flag_button: Button
var report_button: Button
var question_toolbar: HBoxContainer
var nav_palette: HBoxContainer
var question_content: VBoxContainer
var media_container: VBoxContainer
@@ -61,15 +70,21 @@ func _ready() -> void:
QuestionBankService.question_rating_updated.connect(_on_question_rating_updated)
if not QuestionBankService.question_skipped.is_connected(_on_question_skipped):
QuestionBankService.question_skipped.connect(_on_question_skipped)
if not ProfileService.profile_changed.is_connected(_refresh_player_stats):
ProfileService.profile_changed.connect(_refresh_player_stats)
if not QuestionBankService.ratings_changed.is_connected(_refresh_player_stats):
QuestionBankService.ratings_changed.connect(_refresh_player_stats)
if not AuthState.auth_changed.is_connected(_refresh_player_stats):
AuthState.auth_changed.connect(_refresh_player_stats)
var top := HBoxContainer.new()
top.add_theme_constant_override("separation", 12)
progress_label = Label.new()
Style.set_label(progress_label, 18, Style.CYAN)
_set_scaled_label(progress_label, 18, Style.CYAN)
progress_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
top.add_child(progress_label)
timer_label = Label.new()
Style.set_label(timer_label, 18, Style.AMBER)
_set_scaled_label(timer_label, 18, Style.AMBER)
timer_label.visible = false
timer_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
timer_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
@@ -79,9 +94,30 @@ func _ready() -> void:
toolbar_spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
top.add_child(toolbar_spacer)
var toolbar := HBoxContainer.new()
toolbar.add_theme_constant_override("separation", 8)
toolbar.alignment = BoxContainer.ALIGNMENT_END
player_stats_label = Label.new()
player_stats_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
_set_scaled_label(player_stats_label, 15, Style.MUTED)
top.add_child(player_stats_label)
question_toolbar = HBoxContainer.new()
question_toolbar.add_theme_constant_override("separation", 8)
question_toolbar.alignment = BoxContainer.ALIGNMENT_END
text_smaller_button = Button.new()
text_smaller_button.text = "A-"
text_smaller_button.tooltip_text = "Decrease text size"
text_smaller_button.custom_minimum_size = Vector2(44, 44)
Style.style_chip(text_smaller_button, Style.CYAN)
text_smaller_button.pressed.connect(func(): _adjust_question_text_scale(-TEXT_SCALE_STEP))
question_toolbar.add_child(text_smaller_button)
text_larger_button = Button.new()
text_larger_button.text = "A+"
text_larger_button.tooltip_text = "Increase text size"
text_larger_button.custom_minimum_size = Vector2(44, 44)
Style.style_chip(text_larger_button, Style.CYAN)
text_larger_button.pressed.connect(func(): _adjust_question_text_scale(TEXT_SCALE_STEP))
question_toolbar.add_child(text_larger_button)
labs_button = Button.new()
labs_button.tooltip_text = "Review lab results"
@@ -89,7 +125,7 @@ func _ready() -> void:
labs_button.custom_minimum_size = Vector2(44, 44)
Style.style_chip(labs_button, Style.LIME)
labs_button.pressed.connect(_show_labs_modal)
toolbar.add_child(labs_button)
question_toolbar.add_child(labs_button)
var clear_highlight_button := Button.new()
clear_highlight_button.tooltip_text = "Clear highlights"
@@ -97,7 +133,7 @@ func _ready() -> void:
clear_highlight_button.custom_minimum_size = Vector2(44, 44)
Style.style_chip(clear_highlight_button, Style.CYAN)
clear_highlight_button.pressed.connect(_clear_current_highlights)
toolbar.add_child(clear_highlight_button)
question_toolbar.add_child(clear_highlight_button)
flag_button = Button.new()
flag_button.toggle_mode = true
@@ -109,16 +145,16 @@ func _ready() -> void:
flagged = value
_refresh_toolbar_state()
)
toolbar.add_child(flag_button)
question_toolbar.add_child(flag_button)
report_button = Button.new()
report_button.tooltip_text = "Report question"
report_button.icon = _make_report_icon()
report_button.custom_minimum_size = Vector2(44, 44)
report_button.pressed.connect(_toggle_report_current_question)
toolbar.add_child(report_button)
question_toolbar.add_child(report_button)
top.add_child(toolbar)
top.add_child(question_toolbar)
add_child(top)
nav_palette = HBoxContainer.new()
@@ -151,6 +187,7 @@ func _ready() -> void:
stem_label.selection_enabled = true
stem_label.scroll_active = false
stem_label.custom_minimum_size = Vector2(0, 96)
_set_scaled_rich_text(stem_label, 18)
question_content.add_child(stem_label)
media_container = VBoxContainer.new()
@@ -159,15 +196,20 @@ func _ready() -> void:
media_container.add_theme_constant_override("separation", 10)
question_content.add_child(media_container)
var lead_margin := MarginContainer.new()
lead_margin.add_theme_constant_override("margin_top", 12)
lead_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL
question_content.add_child(lead_margin)
lead_label = Label.new()
lead_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
Style.set_label(lead_label, 21, Style.TEXT)
question_content.add_child(lead_label)
_set_scaled_label(lead_label, 21, Style.TEXT)
lead_margin.add_child(lead_label)
question_elo_label = Label.new()
question_elo_label.visible = false
question_elo_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
Style.set_label(question_elo_label, 16, Style.AMBER)
_set_scaled_label(question_elo_label, 16, Style.AMBER)
question_content.add_child(question_elo_label)
for index in range(5):
@@ -175,6 +217,7 @@ func _ready() -> void:
button.custom_minimum_size = Vector2(0, 64)
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
Style.style_answer_button(button)
_set_scaled_button_font(button, 17)
button.pressed.connect(func(i := index): _select_choice(i))
button.gui_input.connect(func(event: InputEvent, i := index): _choice_gui_input(event, i))
choice_buttons.append(button)
@@ -192,7 +235,7 @@ func _ready() -> void:
percent_label.offset_top = 8
percent_label.offset_right = -18
percent_label.offset_bottom = 40
Style.set_label(percent_label, 18, Style.TEXT)
_set_scaled_label(percent_label, 18, Style.TEXT)
button.add_child(percent_label)
choice_percent_labels.append(percent_label)
@@ -207,7 +250,7 @@ func _ready() -> void:
top_label.offset_top = 10
top_label.offset_right = -96
top_label.offset_bottom = -252
Style.set_label(top_label, 16, Style.RED)
_set_scaled_label(top_label, 16, Style.RED)
button.add_child(top_label)
choice_top_labels.append(top_label)
@@ -236,6 +279,7 @@ func _ready() -> void:
explanation_label.scroll_active = false
explanation_label.custom_minimum_size = Vector2(0, 150)
explanation_label.visible = false
_set_scaled_rich_text(explanation_label, 17)
question_content.add_child(explanation_label)
var actions := HBoxContainer.new()
@@ -311,6 +355,8 @@ func _input(event: InputEvent) -> void:
elif highlight_drag_started_in_stem:
highlight_drag_started_in_stem = false
call_deferred("_commit_current_text_selection")
elif _should_clear_selection_from_click(mouse_event):
_clear_selected_choice()
func _load_question() -> void:
@@ -328,13 +374,13 @@ func _load_question() -> void:
question_elo_label.visible = false
question_elo_label.text = ""
next_button.text = "Submit"
next_button.disabled = false
next_button.disabled = true
skip_button.disabled = false
skip_button.visible = BlockSessionService.session_kind == "core"
var question := BlockSessionService.get_current_question()
if question.is_empty():
main.show_screen("dashboard")
main.show_screen("selection", "step3")
return
progress_label.visible = BlockSessionService.session_kind != "core"
progress_label.text = "" if BlockSessionService.session_kind == "core" else "Question %s" % BlockSessionService.get_progress_text()
@@ -348,7 +394,9 @@ func _load_question() -> void:
lead_label.text = question.get("lead_in", "")
_refresh_labs_button()
var choices: Array = question.get("choices", [])
presented_choices = question.get("choices", []).duplicate(true)
presented_choices.shuffle()
var choices := presented_choices
for index in range(choice_buttons.size()):
var button := choice_buttons[index]
button.visible = index < choices.size()
@@ -357,6 +405,9 @@ func _load_question() -> void:
var choice: Dictionary = choices[index]
_set_choice_button_text(index, choice)
button.modulate = Color.WHITE
_style_choice_button_for_state(index, str(choice.get("id", "")))
_refresh_submit_state()
var saved_answer = BlockSessionService.get_answer_for_index(BlockSessionService.current_index)
if saved_answer != null:
@@ -372,28 +423,78 @@ func _load_question() -> void:
else:
_apply_saved_choice_state()
next_button.text = "Next"
next_button.disabled = false
_update_question_elo_label(true)
_refresh_nav_palette()
_refresh_toolbar_state()
_refresh_player_stats()
func _select_choice(index: int) -> void:
if pending_correct_advance or followup_required:
return
var question := BlockSessionService.get_current_question()
var choices: Array = question.get("choices", [])
var choices := _get_presented_choices()
if index >= choices.size() or (BlockSessionService.mode == "Tutor" and not current_answer.is_empty()):
return
if locked_wrong_choice_ids.has(str(choices[index].get("id", ""))):
return
selected_choice = str(choices[index].get("id", ""))
var choice_id := str(choices[index].get("id", ""))
selected_choice = "" if selected_choice == choice_id else choice_id
if BlockSessionService.mode == "Timed":
current_answer = {}
next_button.text = "Submit"
_apply_saved_choice_state()
func _clear_selected_choice() -> void:
if selected_choice == "":
return
selected_choice = ""
if BlockSessionService.mode == "Timed":
current_answer = {}
next_button.text = "Submit"
_apply_saved_choice_state()
func _should_clear_selection_from_click(mouse_event: InputEventMouseButton) -> bool:
if selected_choice == "" or followup_required or not current_answer.is_empty():
return false
if mouse_event.pressed or mouse_event.button_index != MOUSE_BUTTON_LEFT:
return false
var pointer_position := mouse_event.global_position
if stem_label.get_global_rect().has_point(pointer_position):
return false
if lead_label.get_global_rect().has_point(pointer_position):
return false
if explanation_label.visible and explanation_label.get_global_rect().has_point(pointer_position):
return false
if labs_overlay != null and labs_overlay.visible:
return false
if skip_button != null and skip_button.get_global_rect().has_point(pointer_position):
return false
if next_button != null and next_button.get_global_rect().has_point(pointer_position):
return false
if question_toolbar != null and question_toolbar.get_global_rect().has_point(pointer_position):
return false
if labs_button != null and labs_button.visible and labs_button.get_global_rect().has_point(pointer_position):
return false
if flag_button != null and flag_button.get_global_rect().has_point(pointer_position):
return false
if report_button != null and report_button.get_global_rect().has_point(pointer_position):
return false
for button in choice_buttons:
if button.visible and button.get_global_rect().has_point(pointer_position):
return false
for panel in choice_followup_panels:
if panel.visible and panel.get_global_rect().has_point(pointer_position):
return false
for child in media_container.get_children():
if child is Control and child.get_global_rect().has_point(pointer_position):
return false
return true
func _skip_current_question() -> void:
if pending_correct_advance or not current_answer.is_empty() or BlockSessionService.session_kind != "core":
return
@@ -422,8 +523,7 @@ func _choice_gui_input(event: InputEvent, index: int) -> void:
if not current_answer.is_empty():
return
var question := BlockSessionService.get_current_question()
var choices: Array = question.get("choices", [])
var choices := _get_presented_choices()
if index >= choices.size():
return
@@ -442,8 +542,7 @@ func _choice_gui_input(event: InputEvent, index: int) -> void:
func _toggle_eliminate_selected() -> void:
if pending_correct_advance or followup_required or selected_choice == "" or not current_answer.is_empty():
return
var question := BlockSessionService.get_current_question()
var choices: Array = question.get("choices", [])
var choices := _get_presented_choices()
for index in range(choices.size()):
if str(choices[index].get("id", "")) == selected_choice:
if eliminated.has(selected_choice):
@@ -499,8 +598,9 @@ func _show_explanation() -> void:
var correct_id := str(question.get("correct_choice_id", ""))
for button in choice_buttons:
button.disabled = true
for index in range(question.get("choices", []).size()):
var choice: Dictionary = question["choices"][index]
var choices := _get_presented_choices()
for index in range(choices.size()):
var choice: Dictionary = choices[index]
var id := str(choice.get("id", ""))
_set_choice_button_text(index, choice)
if id == correct_id:
@@ -525,7 +625,7 @@ func _advance_or_finish() -> void:
func _finish() -> void:
if BlockSessionService.session_kind == "core":
BlockSessionService.finish_block()
main.show_screen("dashboard")
main.show_screen("selection", "step3")
return
var results := BlockSessionService.finish_block()
main.show_screen("review", results)
@@ -534,7 +634,7 @@ func _finish() -> void:
func _exit_run() -> void:
BlockSessionService.resume_current_question_timer()
BlockSessionService.finish_block()
main.show_screen("dashboard")
main.show_screen("selection", "step3")
func _update_timer_bar() -> void:
@@ -690,6 +790,61 @@ func _refresh_toolbar_state() -> void:
Style.style_chip(report_button, Style.RED if reported_question_ids.has(question_id) else Style.CYAN)
func _refresh_player_stats() -> void:
if player_stats_label == null:
return
var display_name := AuthState.username if AuthState.username != "" else AuthState.role
player_stats_label.text = "Rating: %d | %s" % [
QuestionBankService.overall_elo,
display_name
]
func _adjust_question_text_scale(delta: float) -> void:
var next_scale: float = clamp(question_text_scale + delta, TEXT_SCALE_MIN, TEXT_SCALE_MAX)
if is_equal_approx(next_scale, question_text_scale):
return
question_text_scale = next_scale
_refresh_scaled_fonts(self)
_render_stem()
_apply_saved_choice_state()
func _set_scaled_label(label: Label, base_size: int, color: Color = Style.TEXT) -> void:
label.set_meta("question_base_font_size", base_size)
label.add_theme_color_override("font_color", color)
label.add_theme_font_size_override("font_size", _scaled_font_size(base_size))
func _set_scaled_button_font(button: Button, base_size: int) -> void:
button.set_meta("question_base_font_size", base_size)
button.add_theme_font_size_override("font_size", _scaled_font_size(base_size))
func _set_scaled_rich_text(label: RichTextLabel, base_size: int) -> void:
label.set_meta("question_base_font_size", base_size)
label.add_theme_font_size_override("normal_font_size", _scaled_font_size(base_size))
label.add_theme_font_size_override("bold_font_size", _scaled_font_size(base_size))
func _refresh_scaled_fonts(node: Node) -> void:
if node.has_meta("question_base_font_size"):
var base_size := int(node.get_meta("question_base_font_size"))
if node is Label:
node.add_theme_font_size_override("font_size", _scaled_font_size(base_size))
elif node is Button:
node.add_theme_font_size_override("font_size", _scaled_font_size(base_size))
elif node is RichTextLabel:
node.add_theme_font_size_override("normal_font_size", _scaled_font_size(base_size))
node.add_theme_font_size_override("bold_font_size", _scaled_font_size(base_size))
for child in node.get_children():
_refresh_scaled_fonts(child)
func _scaled_font_size(base_size: int) -> int:
return int(round(float(base_size) * question_text_scale))
func _refresh_labs_button() -> void:
if labs_button == null:
return
@@ -728,7 +883,7 @@ func _load_question_media(question: Dictionary) -> void:
var status := Label.new()
status.text = "Loading image..."
status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
Style.set_label(status, 15, Style.MUTED)
_set_scaled_label(status, 15, Style.MUTED)
image_box.add_child(status)
var texture_rect := TextureRect.new()
@@ -847,7 +1002,7 @@ func _build_labs_modal() -> void:
var title := Label.new()
title.text = "Lab Results"
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
Style.set_label(title, 26, Style.TEXT)
_set_scaled_label(title, 26, Style.TEXT)
header.add_child(title)
var close_button := Button.new()
@@ -926,7 +1081,7 @@ func _add_lab_table_label(parent: GridContainer, text: String, color: Color, is_
label.text = text if text.strip_edges() != "" else "-"
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
Style.set_label(label, 16 if not is_header else 17, color)
_set_scaled_label(label, 16 if not is_header else 17, color)
parent.add_child(label)
@@ -983,8 +1138,7 @@ func _subtract_highlight_range(highlights: Array, start: int, end: int) -> bool:
func _apply_saved_choice_state() -> void:
var question := BlockSessionService.get_current_question()
var choices: Array = question.get("choices", [])
var choices := _get_presented_choices()
for index in range(choices.size()):
var id := str(choices[index].get("id", ""))
var has_followup_panel := index < choice_followup_panels.size() and choice_followup_panels[index].visible
@@ -1008,10 +1162,25 @@ func _apply_saved_choice_state() -> void:
var id := str(choices[index].get("id", ""))
choice_buttons[index].disabled = id != active_followup_choice_id
_refresh_submit_state()
func _refresh_submit_state() -> void:
if next_button == null:
return
if pending_correct_advance or followup_required:
next_button.disabled = true
return
if not current_answer.is_empty():
next_button.disabled = false
return
next_button.disabled = selected_choice == ""
func _set_choice_button_text(index: int, choice: Dictionary) -> void:
var choice_id := str(choice.get("id", ""))
var choice_text := str(choice.get("text", ""))
var display_label := _display_label_for_index(index)
var has_visible_followup := index < choice_followup_panels.size() and choice_followup_panels[index].visible
choice_buttons[index].custom_minimum_size = _choice_row_size(index, choice_id, has_visible_followup)
_set_choice_percent_label(index, "")
@@ -1021,21 +1190,21 @@ func _set_choice_button_text(index: int, choice: Dictionary) -> void:
if percentage >= 0:
_set_choice_percent_label(index, "%d%%" % percentage)
if has_visible_followup:
var wrong_text := "%s. %s" % [choice_id, choice_text]
var wrong_text := "%s. %s" % [display_label, choice_text]
choice_buttons[index].text = ""
_set_choice_top_label(index, wrong_text)
else:
var wrong_text := "%s. %s" % [choice_id, _strike_text(choice_text)]
var wrong_text := "%s. %s" % [display_label, _strike_text(choice_text)]
choice_buttons[index].text = wrong_text
elif _should_show_correct_choice_percentage(choice_id):
var correct_percentage := _get_choice_selection_percentage(choice_id)
if correct_percentage >= 0:
_set_choice_percent_label(index, "%d%%" % correct_percentage)
choice_buttons[index].text = "%s. %s" % [choice_id, choice_text]
choice_buttons[index].text = "%s. %s" % [display_label, choice_text]
elif eliminated.has(choice_id):
choice_buttons[index].text = "%s. [eliminated] %s" % [choice_id, _strike_text(choice_text)]
choice_buttons[index].text = "%s. [eliminated] %s" % [display_label, _strike_text(choice_text)]
else:
choice_buttons[index].text = "%s. %s" % [choice_id, choice_text]
choice_buttons[index].text = "%s. %s" % [display_label, choice_text]
func _style_choice_button_for_state(index: int, choice_id: String) -> void:
@@ -1095,7 +1264,7 @@ func _should_show_correct_choice_percentage(choice_id: String) -> bool:
func _apply_correct_answer_preview() -> void:
var question := BlockSessionService.get_current_question()
var correct_id := str(question.get("correct_choice_id", ""))
var choices: Array = question.get("choices", [])
var choices := _get_presented_choices()
for index in range(choices.size()):
if str(choices[index].get("id", "")) == correct_id:
choice_buttons[index].modulate = Color(0.54, 1.0, 0.58)
@@ -1127,11 +1296,14 @@ func _build_followup_panel(index: int, choice_id: String) -> void:
var panel := choice_followup_panels[index]
var content := choice_followup_contents[index]
var followup := _get_followup_for_choice(choice_id)
var followup_choices: Array = followup.get("choices", []).duplicate(true)
followup_choices.shuffle()
followup["choices"] = followup_choices
followup_states[choice_id] = {
"choice_id": choice_id,
"choice_text": _choice_text_for_id(choice_id),
"prompt": str(followup.get("prompt", "")),
"choices": followup.get("choices", []),
"choices": followup_choices,
"correct_choice_id": str(followup.get("correct_choice_id", "")),
"teaching_point": str(followup.get("teaching_point", "")),
"selected_option_id": "",
@@ -1145,19 +1317,21 @@ func _build_followup_panel(index: int, choice_id: String) -> void:
var prompt := Label.new()
prompt.text = str(followup.get("prompt", "What finding in the stem makes this answer less appropriate?"))
prompt.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
Style.set_label(prompt, 14, Style.TEXT)
_set_scaled_label(prompt, 14, Style.TEXT)
content.add_child(prompt)
var choices: Array = followup.get("choices", [])
var choices: Array = followup_choices
if bool(followup.get("has_authored_choices", false)):
for option in choices:
for option_index in range(choices.size()):
var option = choices[option_index]
if typeof(option) != TYPE_DICTIONARY:
continue
var option_button := Button.new()
option_button.text = "%s. %s" % [str(option.get("id", "")), str(option.get("text", ""))]
option_button.text = "%s. %s" % [_display_label_for_index(option_index), str(option.get("text", ""))]
option_button.alignment = HORIZONTAL_ALIGNMENT_LEFT
option_button.custom_minimum_size = Vector2(0, 32)
Style.style_chip(option_button, Style.CYAN)
_set_scaled_button_font(option_button, 15)
option_button.set_meta("option_id", str(option.get("id", "")))
option_button.pressed.connect(func(option_id := str(option.get("id", "")), selected_button := option_button, followup_data := followup):
_answer_followup(option_id, selected_button, followup_data)
@@ -1173,6 +1347,7 @@ func _build_followup_panel(index: int, choice_id: String) -> void:
continue_button.text = "Continue"
continue_button.custom_minimum_size = Vector2(130, 32)
Style.style_chip(continue_button, Style.CYAN)
_set_scaled_button_font(continue_button, 15)
continue_button.pressed.connect(func(followup_data := followup): _resolve_followup(followup_data, null, false, false))
action_row.add_child(continue_button)
@@ -1209,7 +1384,7 @@ func _resolve_followup(followup: Dictionary, selected_button: Button, style_sele
var teaching := Label.new()
teaching.text = str(followup.get("teaching_point", "Review the relevant stem finding before choosing again."))
teaching.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
Style.set_label(teaching, 14, Style.AMBER)
_set_scaled_label(teaching, 14, Style.AMBER)
choice_followup_contents[index].add_child(teaching)
if followup_states.has(resolved_choice_id):
@@ -1280,10 +1455,11 @@ func _render_followup_state(choice_id: String) -> void:
func _build_collapsed_followup(index: int, state: Dictionary) -> void:
var content := choice_followup_contents[index]
var summary := Button.new()
summary.text = "Review: %s. %s" % [str(state.get("choice_id", "")), str(state.get("choice_text", ""))]
summary.text = "Review: %s. %s" % [_display_label_for_choice_id(str(state.get("choice_id", ""))), str(state.get("choice_text", ""))]
summary.alignment = HORIZONTAL_ALIGNMENT_LEFT
summary.custom_minimum_size = Vector2(0, 40)
Style.style_chip(summary, Style.AMBER)
_set_scaled_button_font(summary, 15)
summary.gui_input.connect(func(_event: InputEvent): _mark_review_input_handled())
summary.pressed.connect(func(choice_id := str(state.get("choice_id", ""))): _toggle_followup_state(choice_id))
content.add_child(summary)
@@ -1292,10 +1468,11 @@ func _build_collapsed_followup(index: int, state: Dictionary) -> void:
func _build_readonly_followup(index: int, state: Dictionary) -> void:
var content := choice_followup_contents[index]
var summary := Button.new()
summary.text = "Hide review: %s. %s" % [str(state.get("choice_id", "")), str(state.get("choice_text", ""))]
summary.text = "Hide review: %s. %s" % [_display_label_for_choice_id(str(state.get("choice_id", ""))), str(state.get("choice_text", ""))]
summary.alignment = HORIZONTAL_ALIGNMENT_LEFT
summary.custom_minimum_size = Vector2(0, 36)
Style.style_chip(summary, Style.AMBER)
_set_scaled_button_font(summary, 15)
summary.gui_input.connect(func(_event: InputEvent): _mark_review_input_handled())
summary.pressed.connect(func(choice_id := str(state.get("choice_id", ""))): _toggle_followup_state(choice_id))
content.add_child(summary)
@@ -1303,20 +1480,20 @@ func _build_readonly_followup(index: int, state: Dictionary) -> void:
var prompt := Label.new()
prompt.text = str(state.get("prompt", ""))
prompt.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
Style.set_label(prompt, 14, Style.TEXT)
_set_scaled_label(prompt, 14, Style.TEXT)
content.add_child(prompt)
var selected := Label.new()
var selected_text := str(state.get("selected_option_text", ""))
selected.text = "Your follow-up answer: %s" % selected_text if selected_text != "" else "Follow-up reviewed."
selected.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
Style.set_label(selected, 14, Style.LIME if bool(state.get("was_correct", false)) else Style.RED)
_set_scaled_label(selected, 14, Style.LIME if bool(state.get("was_correct", false)) else Style.RED)
content.add_child(selected)
var teaching := Label.new()
teaching.text = str(state.get("teaching_point", ""))
teaching.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
Style.set_label(teaching, 14, Style.AMBER)
_set_scaled_label(teaching, 14, Style.AMBER)
content.add_child(teaching)
@@ -1361,9 +1538,23 @@ func _get_followup_for_choice(choice_id: String) -> Dictionary:
}
func _get_presented_choices() -> Array:
if not presented_choices.is_empty():
return presented_choices
return BlockSessionService.get_current_question().get("choices", [])
func _display_label_for_index(index: int) -> String:
return String.chr(65 + index)
func _display_label_for_choice_id(choice_id: String) -> String:
var index := _choice_index_for_id(choice_id)
return _display_label_for_index(index) if index >= 0 else choice_id
func _choice_index_for_id(choice_id: String) -> int:
var question := BlockSessionService.get_current_question()
var choices: Array = question.get("choices", [])
var choices := _get_presented_choices()
for index in range(choices.size()):
if str(choices[index].get("id", "")) == choice_id:
return index
@@ -1371,8 +1562,7 @@ func _choice_index_for_id(choice_id: String) -> int:
func _choice_text_for_id(choice_id: String) -> String:
var question := BlockSessionService.get_current_question()
var choices: Array = question.get("choices", [])
var choices := _get_presented_choices()
for choice in choices:
if typeof(choice) == TYPE_DICTIONARY and str(choice.get("id", "")) == choice_id:
return str(choice.get("text", ""))