From 5ba7af9d3ebce4c3ace111b72c161ce249174a79 Mon Sep 17 00:00:00 2001 From: hz Date: Thu, 23 Jul 2026 01:23:53 -0500 Subject: [PATCH] Redesign flag/report icons as vector assets and dock review panels inside their choice card Flag question now uses a clean vector flag (dark/light SVG variants, replacing a hand-drawn pixel-art icon that got muddied by the shared toolbar icon-tint override). Report question is now a megaphone instead of a second flag glyph, since both toolbar buttons looked identical before. The correct-answer explanation and the wrong-answer follow-up quiz previously rendered as separate bordered boxes sitting below the entire choice list. Both now dock inside their own choice's card: the explanation panel reparents onto the correct choice's row, the follow-up panel was already there but visually detached. A shared _attached_panel_box style drops the shared border/corner between the choice button and its panel so they read as one continuous card. --- assets/icons/medmind/flag-dark.svg | 4 + assets/icons/medmind/flag-dark.svg.import | 43 ++++++ assets/icons/medmind/flag-light.svg | 4 + assets/icons/medmind/flag-light.svg.import | 43 ++++++ assets/icons/medmind/report-question-dark.svg | 6 +- .../medmind/report-question-dark.svg.import | 2 +- .../icons/medmind/report-question-light.svg | 6 +- .../medmind/report-question-light.svg.import | 2 +- scripts/ui/question_player.gd | 135 ++++++++++-------- 9 files changed, 181 insertions(+), 64 deletions(-) create mode 100644 assets/icons/medmind/flag-dark.svg create mode 100644 assets/icons/medmind/flag-dark.svg.import create mode 100644 assets/icons/medmind/flag-light.svg create mode 100644 assets/icons/medmind/flag-light.svg.import 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