diff --git a/assets/icons/medmind/flag-dark.svg b/assets/icons/medmind/flag-dark.svg
new file mode 100644
index 0000000..61b57f3
--- /dev/null
+++ b/assets/icons/medmind/flag-dark.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/medmind/flag-dark.svg.import b/assets/icons/medmind/flag-dark.svg.import
new file mode 100644
index 0000000..3e8ff28
--- /dev/null
+++ b/assets/icons/medmind/flag-dark.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://pffbgabfvqoi"
+path="res://.godot/imported/flag-dark.svg-991f8446e8d6e35dda3f6918b79c2529.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/icons/medmind/flag-dark.svg"
+dest_files=["res://.godot/imported/flag-dark.svg-991f8446e8d6e35dda3f6918b79c2529.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/icons/medmind/flag-light.svg b/assets/icons/medmind/flag-light.svg
new file mode 100644
index 0000000..c760024
--- /dev/null
+++ b/assets/icons/medmind/flag-light.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/medmind/flag-light.svg.import b/assets/icons/medmind/flag-light.svg.import
new file mode 100644
index 0000000..c47f1b6
--- /dev/null
+++ b/assets/icons/medmind/flag-light.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://rrjvnmtbkc2c"
+path="res://.godot/imported/flag-light.svg-028b26500c0b5f2e10e3a85231e2e56d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/icons/medmind/flag-light.svg"
+dest_files=["res://.godot/imported/flag-light.svg-028b26500c0b5f2e10e3a85231e2e56d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/icons/medmind/report-question-dark.svg b/assets/icons/medmind/report-question-dark.svg
index 442a927..007fb72 100644
--- a/assets/icons/medmind/report-question-dark.svg
+++ b/assets/icons/medmind/report-question-dark.svg
@@ -1,5 +1,5 @@
diff --git a/assets/icons/medmind/report-question-dark.svg.import b/assets/icons/medmind/report-question-dark.svg.import
index c2a2de3..9d75388 100644
--- a/assets/icons/medmind/report-question-dark.svg.import
+++ b/assets/icons/medmind/report-question-dark.svg.import
@@ -2,7 +2,7 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://d1rf1y5ym0abp"
+uid="uid://2h8hksovbjvl"
path="res://.godot/imported/report-question-dark.svg-0c56358e403130829c20d7684d988f4f.ctex"
metadata={
"vram_texture": false
diff --git a/assets/icons/medmind/report-question-light.svg b/assets/icons/medmind/report-question-light.svg
index 425c867..acb7af3 100644
--- a/assets/icons/medmind/report-question-light.svg
+++ b/assets/icons/medmind/report-question-light.svg
@@ -1,5 +1,5 @@
diff --git a/assets/icons/medmind/report-question-light.svg.import b/assets/icons/medmind/report-question-light.svg.import
index 856d312..8b09f03 100644
--- a/assets/icons/medmind/report-question-light.svg.import
+++ b/assets/icons/medmind/report-question-light.svg.import
@@ -2,7 +2,7 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://btqfcwm8c1ymy"
+uid="uid://gljo3k630b6f"
path="res://.godot/imported/report-question-light.svg-d6a637ee28802ff5e54da98728d1db20.ctex"
metadata={
"vram_texture": false
diff --git a/scripts/ui/question_player.gd b/scripts/ui/question_player.gd
index d625eb9..a8cfe39 100644
--- a/scripts/ui/question_player.gd
+++ b/scripts/ui/question_player.gd
@@ -9,6 +9,8 @@ const LABS_DARK_ICON := preload("res://assets/icons/medmind/labs-dark.svg")
const LABS_LIGHT_ICON := preload("res://assets/icons/medmind/labs-light.svg")
const REPORT_QUESTION_DARK_ICON := preload("res://assets/icons/medmind/report-question-dark.svg")
const REPORT_QUESTION_LIGHT_ICON := preload("res://assets/icons/medmind/report-question-light.svg")
+const FLAG_DARK_ICON := preload("res://assets/icons/medmind/flag-dark.svg")
+const FLAG_LIGHT_ICON := preload("res://assets/icons/medmind/flag-light.svg")
const BADGE_CIRCLE_OUTLINE_ICON := preload("res://assets/icons/medmind/badge-circle-outline.svg")
const BADGE_CIRCLE_FILLED_ICON := preload("res://assets/icons/medmind/badge-circle-filled.svg")
const FEEDBACK_FADE_SECONDS := 0.45
@@ -274,7 +276,6 @@ func _ready() -> void:
flag_button = Button.new()
flag_button.toggle_mode = true
flag_button.tooltip_text = "Flag question"
- flag_button.icon = _make_flag_icon()
flag_button.custom_minimum_size = Vector2(44, 44)
Style.style_chip(flag_button, Style.AMBER)
UISoundService.set_button_sound(flag_button, "toggle")
@@ -434,7 +435,7 @@ func _ready() -> void:
for index in range(5):
var choice_row := VBoxContainer.new()
- choice_row.add_theme_constant_override("separation", 6)
+ choice_row.add_theme_constant_override("separation", 0)
choices_container.add_child(choice_row)
choice_rows.append(choice_row)
@@ -701,7 +702,7 @@ func _question_content_box(dark: bool) -> StyleBoxFlat:
return box
-func _answer_box(dark: bool, state: String) -> StyleBoxFlat:
+func _answer_box(dark: bool, state: String, attached: bool = false) -> StyleBoxFlat:
var box := StyleBoxFlat.new()
box.content_margin_left = 62
box.content_margin_right = 82
@@ -727,6 +728,46 @@ func _answer_box(dark: bool, state: String) -> StyleBoxFlat:
box.border_color = border
box.set_border_width_all(2)
box.set_corner_radius_all(14)
+ if attached:
+ # A review/follow-up panel is docked directly beneath this choice - drop the
+ # shared edge so the two read as one continuous card instead of two stacked boxes.
+ box.border_width_bottom = 0
+ box.corner_radius_bottom_left = 0
+ box.corner_radius_bottom_right = 0
+ return box
+
+
+func _attached_panel_box(dark: bool, accent: Color) -> StyleBoxFlat:
+ var box := StyleBoxFlat.new()
+ box.bg_color = Color("17183b") if dark else Color("eff4ff")
+ if accent == Brand.SUCCESS:
+ box.bg_color = Color("08251d") if dark else Color("ecfdf3")
+ elif accent == Color("fb7185"):
+ box.bg_color = Color("32131d") if dark else Color("fff1f2")
+ box.border_color = accent
+ box.border_width_left = 2
+ box.border_width_right = 2
+ box.border_width_bottom = 2
+ box.border_width_top = 0
+ box.corner_radius_top_left = 0
+ box.corner_radius_top_right = 0
+ box.corner_radius_bottom_left = 14
+ box.corner_radius_bottom_right = 14
+ box.content_margin_left = 12
+ box.content_margin_right = 12
+ box.content_margin_top = 10
+ box.content_margin_bottom = 10
+ return box
+
+
+func _correct_review_panel_box(dark: bool) -> StyleBoxFlat:
+ # correct_review_panel already pads its content via an inner MarginContainer,
+ # so skip the shared attached-panel content margin to avoid doubling it up.
+ var box := _attached_panel_box(dark, Brand.SUCCESS)
+ box.content_margin_left = 0
+ box.content_margin_right = 0
+ box.content_margin_top = 0
+ box.content_margin_bottom = 0
return box
@@ -768,7 +809,7 @@ func _style_theme_toggle_button(dark: bool) -> void:
theme_button.add_theme_constant_override("h_separation", 0)
-func _style_asset_toolbar_button(button: Button, texture: Texture2D, dark: bool) -> void:
+func _style_asset_toolbar_button(button: Button, texture: Texture2D, dark: bool, active: bool = false) -> void:
if button == null:
return
button.text = ""
@@ -782,7 +823,7 @@ func _style_asset_toolbar_button(button: Button, texture: Texture2D, dark: bool)
button.size_flags_vertical = Control.SIZE_SHRINK_CENTER
button.add_theme_constant_override("h_separation", 0)
button.add_theme_constant_override("icon_max_width", 23)
- button.add_theme_stylebox_override("normal", _toolbar_box(dark))
+ button.add_theme_stylebox_override("normal", _toolbar_box(dark, active))
button.add_theme_stylebox_override("hover", _toolbar_box(dark, true))
button.add_theme_stylebox_override("pressed", _toolbar_box(dark, true))
button.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
@@ -813,15 +854,6 @@ func _primary_action_box(dark: bool, state: String) -> StyleBoxFlat:
return box
-func _correct_review_box(dark: bool) -> StyleBoxFlat:
- var box := StyleBoxFlat.new()
- box.bg_color = Color("08251d") if dark else Color("f0fdf4")
- box.border_color = Color(Brand.SUCCESS, 0.62)
- box.set_border_width_all(1)
- box.set_corner_radius_all(7)
- return box
-
-
func _teaching_card_box(dark: bool, accent: Color) -> StyleBoxFlat:
var box := StyleBoxFlat.new()
box.bg_color = Color(accent, 0.08 if dark else 0.045)
@@ -906,15 +938,15 @@ func _apply_question_theme() -> void:
if wrong_options_heading != null:
wrong_options_heading.add_theme_color_override("font_color", Brand.text_color(dark))
if correct_review_panel != null:
- correct_review_panel.add_theme_stylebox_override("panel", _correct_review_box(dark))
+ correct_review_panel.add_theme_stylebox_override("panel", _correct_review_panel_box(dark))
question_elo_label.add_theme_color_override("font_color", Brand.AMBER)
var navigator_heading := navigator_popup.find_child("NavigatorHeading", true, false) as Label
if navigator_heading != null:
navigator_heading.add_theme_color_override("font_color", Brand.text_color(dark))
- for control in [text_smaller_button, text_larger_button, flag_button, bookmark_button, notes_button, flashcard_button, app_feedback_button]:
+ for control in [text_smaller_button, text_larger_button, bookmark_button, notes_button, flashcard_button, app_feedback_button]:
if control == null:
continue
- var active: bool = (control == flag_button and flagged) or (control == bookmark_button and BookmarkService.is_bookmarked(str(BlockSessionService.get_current_question().get("id", ""))))
+ var active: bool = control == bookmark_button and BookmarkService.is_bookmarked(str(BlockSessionService.get_current_question().get("id", "")))
control.add_theme_stylebox_override("normal", _toolbar_box(dark, active))
control.add_theme_stylebox_override("hover", _toolbar_box(dark, true))
control.add_theme_stylebox_override("pressed", _toolbar_box(dark, true))
@@ -923,6 +955,7 @@ func _apply_question_theme() -> void:
_style_asset_toolbar_button(labs_button, LABS_DARK_ICON if dark else LABS_LIGHT_ICON, dark)
_style_asset_toolbar_button(clear_highlight_button, CLEAR_HIGHLIGHT_DARK_ICON if dark else CLEAR_HIGHLIGHT_LIGHT_ICON, dark)
_style_asset_toolbar_button(report_button, REPORT_QUESTION_DARK_ICON if dark else REPORT_QUESTION_LIGHT_ICON, dark)
+ _style_asset_toolbar_button(flag_button, FLAG_DARK_ICON if dark else FLAG_LIGHT_ICON, dark, flagged)
_style_theme_toggle_button(dark)
if skip_button != null:
skip_button.add_theme_stylebox_override("normal", _toolbar_box(dark))
@@ -1485,6 +1518,10 @@ func _show_explanation() -> void:
var question := BlockSessionService.get_current_question()
for button in choice_buttons:
button.disabled = true
+ var correct_id := str(question.get("correct_choice_id", ""))
+ _dock_correct_review_panel(correct_id)
+ correct_review_panel.visible = true
+ correct_answer_heading.text = "Correct Answer: %s" % _display_label_for_choice_id(correct_id)
var choices := _get_presented_choices()
for index in range(choices.size()):
var choice: Dictionary = choices[index]
@@ -1495,14 +1532,19 @@ func _show_explanation() -> void:
explanation_plain_text = str(question.get("explanation", ""))
_render_explanation()
explanation_label.visible = true
- correct_review_panel.visible = true
- var correct_id := str(question.get("correct_choice_id", ""))
- correct_answer_heading.text = "Correct Answer: %s" % _display_label_for_choice_id(correct_id)
_update_question_elo_label(true)
_refresh_nav_palette()
_update_content_vertical_alignment()
+func _dock_correct_review_panel(correct_id: String) -> void:
+ var index := _choice_index_for_id(correct_id)
+ if index < 0 or index >= choice_rows.size():
+ return
+ if correct_review_panel.get_parent() != choice_rows[index]:
+ correct_review_panel.reparent(choice_rows[index])
+
+
func _render_answer_review_sections() -> void:
_render_teaching_cards()
_render_review_followups()
@@ -2110,8 +2152,6 @@ func _on_review_pass_completed(summary: Dictionary) -> void:
func refresh_theme_controls() -> void:
if media_viewer_background != null:
media_viewer_background.color = Style.BACKGROUND
- if flag_button != null:
- flag_button.icon = _make_flag_icon()
_refresh_toolbar_state()
_apply_saved_choice_state()
_apply_question_theme()
@@ -3035,11 +3075,12 @@ func _style_choice_button_for_state(index: int, choice_id: String) -> void:
elif eliminated.has(choice_id):
state = "eliminated"
var button := choice_buttons[index]
- var normal_box := _answer_box(dark, state)
+ var attached := _row_has_attached_panel(index)
+ var normal_box := _answer_box(dark, state, attached)
button.add_theme_stylebox_override("normal", normal_box)
button.add_theme_stylebox_override("disabled", normal_box)
- button.add_theme_stylebox_override("pressed", _answer_box(dark, "selected" if state == "default" else state))
- button.add_theme_stylebox_override("hover", _answer_box(dark, "selected" if state == "default" else state))
+ button.add_theme_stylebox_override("pressed", _answer_box(dark, "selected" if state == "default" else state, attached))
+ button.add_theme_stylebox_override("hover", _answer_box(dark, "selected" if state == "default" else state, attached))
var text_color := Brand.muted_color(dark) if state == "eliminated" else Brand.text_color(dark)
button.add_theme_color_override("font_color", text_color)
button.add_theme_color_override("font_disabled_color", text_color)
@@ -3057,6 +3098,16 @@ func _style_choice_button_for_state(index: int, choice_id: String) -> void:
badge.add_theme_color_override("font_color", Color.WHITE if filled else Brand.muted_color(dark))
+func _row_has_attached_panel(index: int) -> bool:
+ if index < 0 or index >= choice_rows.size():
+ return false
+ if index < choice_followup_panels.size() and choice_followup_panels[index].visible:
+ return true
+ if correct_review_panel != null and correct_review_panel.visible and correct_review_panel.get_parent() == choice_rows[index]:
+ return true
+ return false
+
+
func _mark_review_input_handled() -> void:
get_viewport().set_input_as_handled()
@@ -3266,6 +3317,7 @@ func _render_followup_state(choice_id: String) -> void:
else:
_build_resolved_followup(index, state)
choice_followup_panels[index].visible = true
+ _style_choice_button_for_state(index, choice_id)
func _build_resolved_followup(index: int, state: Dictionary) -> void:
@@ -3351,26 +3403,13 @@ func _add_followup_body(content: VBoxContainer, is_correct: bool) -> VBoxContain
func _style_followup_card(index: int, state: Dictionary) -> void:
if index < 0 or index >= choice_followup_panels.size():
return
+ var is_correct := bool(state.get("is_main_correct", false))
choice_followup_panels[index].add_theme_stylebox_override(
"panel",
- _followup_card_box(_followup_is_dark(), bool(state.get("is_main_correct", false)))
+ _attached_panel_box(_followup_is_dark(), Brand.SUCCESS if is_correct else Color("fb7185"))
)
-func _followup_card_box(dark: bool, is_correct: bool) -> StyleBoxFlat:
- var accent := Brand.SUCCESS if is_correct else Color("fb5f6d")
- var box := StyleBoxFlat.new()
- box.bg_color = Color("0b172b") if dark else Color("ffffff")
- box.border_color = Color(accent, 0.72)
- box.set_border_width_all(1)
- box.set_corner_radius_all(7)
- box.content_margin_left = 12
- box.content_margin_right = 12
- box.content_margin_top = 10
- box.content_margin_bottom = 10
- return box
-
-
func _followup_body_box(dark: bool, is_correct: bool) -> StyleBoxFlat:
var accent := Brand.SUCCESS if is_correct else Color("fb5f6d")
var box := StyleBoxFlat.new()
@@ -3529,22 +3568,6 @@ func _update_question_elo_label(show_label: bool) -> void:
question_elo_label.visible = true
-func _make_flag_icon() -> Texture2D:
- var image := Image.create(24, 24, false, Image.FORMAT_RGBA8)
- image.fill(Color(0, 0, 0, 0))
- for y in range(4, 20):
- image.set_pixel(6, y, Style.TEXT)
- image.set_pixel(7, y, Style.TEXT)
- for x in range(8, 18):
- for y in range(4, 11):
- if x < 16 or y < 9:
- image.set_pixel(x, y, Style.AMBER)
- for y in range(11, 14):
- for x in range(8, 14 - (y - 11)):
- image.set_pixel(x, y, Style.AMBER)
- return ImageTexture.create_from_image(image)
-
-
func _refresh_nav_palette() -> void:
if nav_palette == null:
return