Reveal the flashcard answer by tapping the card, not a separate button

Matches the mockup: the "Show Answer" button is replaced by an in-card
"Tap card to reveal answer" hint, and clicking anywhere on the card
reveals it (guarded the same way Space already was - only while at the
live card and not yet revealed).
This commit is contained in:
hz
2026-07-22 17:21:23 -05:00
parent 7f76ca0fdc
commit e8d615985b
3 changed files with 45 additions and 25 deletions
+24 -21
View File
@@ -55,7 +55,7 @@ var back_label: Label
var extra_caption: Label
var extra_label: Label
var card_position_label: Label
var reveal_button: Button
var tap_hint_label: Label
var grade_row: HBoxContainer
var grade_buttons: Dictionary = {}
var undo_button: Button
@@ -184,6 +184,8 @@ func _build() -> void:
card_panel = PanelContainer.new()
card_panel.custom_minimum_size = Vector2(720, 420)
card_panel.add_theme_stylebox_override("panel", Brand.content_card_box(UIThemeService.is_dark_mode(), true, Brand.ROYAL_BLUE))
card_panel.mouse_filter = Control.MOUSE_FILTER_STOP
card_panel.gui_input.connect(_on_card_panel_input)
center.add_child(card_panel)
var card_content := VBoxContainer.new()
card_content.add_theme_constant_override("separation", 20)
@@ -206,6 +208,13 @@ func _build() -> void:
divider.visible = false
card_content.add_child(divider)
var tap_hint_center := CenterContainer.new()
card_content.add_child(tap_hint_center)
tap_hint_label = Label.new()
tap_hint_label.text = "Tap card to reveal answer"
tap_hint_label.visible = false
tap_hint_center.add_child(tap_hint_label)
var answer_block := VBoxContainer.new()
answer_block.add_theme_constant_override("separation", 4)
card_content.add_child(answer_block)
@@ -240,13 +249,6 @@ func _build() -> void:
card_position_label.visible = false
position_row.add_child(card_position_label)
reveal_button = Button.new()
reveal_button.text = "Show Answer (Space)"
reveal_button.custom_minimum_size = Vector2(0, 52)
_style_gradient_button(reveal_button)
reveal_button.pressed.connect(_reveal)
center_column.add_child(reveal_button)
grade_row = HBoxContainer.new()
grade_row.add_theme_constant_override("separation", 10)
grade_row.visible = false
@@ -309,14 +311,6 @@ func _caption_label(text: String) -> Label:
return label
func _style_gradient_button(button: Button) -> void:
button.add_theme_stylebox_override("normal", Brand.gradient_button_box())
button.add_theme_stylebox_override("hover", Brand.gradient_button_box())
button.add_theme_stylebox_override("pressed", Brand.gradient_button_box())
button.add_theme_color_override("font_color", Color.WHITE)
button.add_theme_color_override("font_hover_color", Color.WHITE)
func _style_ghost_button(button: Button, dark: bool) -> void:
var empty := StyleBoxEmpty.new()
button.add_theme_stylebox_override("normal", empty)
@@ -565,6 +559,7 @@ func refresh_theme_controls() -> void:
extra_caption.add_theme_color_override("font_color", Brand.muted_color(dark))
extra_label.add_theme_color_override("font_color", Brand.muted_color(dark))
card_position_label.add_theme_color_override("font_color", Brand.muted_color(dark))
tap_hint_label.add_theme_color_override("font_color", Brand.muted_color(dark))
_style_ghost_button(undo_button, dark)
previous_button.add_theme_stylebox_override("normal", Brand.secondary_button_box(dark))
previous_button.add_theme_stylebox_override("hover", Brand.secondary_button_box(dark, true))
@@ -706,7 +701,7 @@ func _render_history_entry(index: int) -> void:
_render_card()
_reveal()
grade_row.visible = false
reveal_button.visible = false
tap_hint_label.visible = false
var is_graded_entry: bool = entry.get("action", "") == "graded"
var is_last_graded_card: bool = str(entry.get("card_id", "")) == last_card_id
var has_undoable_review: bool = not last_client_review_id.is_empty()
@@ -811,7 +806,7 @@ func _render_loading() -> void:
front_label.visible = true
front_label.text = "Loading..."
_hide_answer()
reveal_button.visible = false
tap_hint_label.visible = false
grade_row.visible = false
@@ -819,7 +814,7 @@ func _render_empty(card: Dictionary) -> void:
empty_label.visible = true
front_label.visible = false
_hide_answer()
reveal_button.visible = false
tap_hint_label.visible = false
grade_row.visible = false
progress_label.text = "0 due"
if typeof(card) == TYPE_DICTIONARY:
@@ -844,7 +839,8 @@ func _render_card() -> void:
front_label.text = str(current_note.get("prompt", ""))
front_label.visible = true
_hide_answer()
reveal_button.visible = true
divider.visible = true
tap_hint_label.visible = true
grade_row.visible = false
progress_label.text = "%d due, %d new available" % [due_remaining, new_remaining]
@@ -866,13 +862,20 @@ func _reveal() -> void:
extra_caption.visible = not extra.is_empty()
extra_label.visible = not extra.is_empty()
extra_label.text = extra
reveal_button.visible = false
tap_hint_label.visible = false
grade_row.visible = true
for rating in grade_buttons:
var button: Button = grade_buttons[rating]
button.text = "%s\n%s" % [rating.capitalize(), _interval_label_for(rating)]
func _on_card_panel_input(event: InputEvent) -> void:
if history_cursor != session_history.size() or revealed:
return
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
_reveal()
func _interval_label_for(rating: String) -> String:
for option in rating_options:
if str(option.get("rating", "")) == rating:
+2 -2
View File
@@ -135,8 +135,8 @@ func _run() -> void:
screen._go_next()
_assert(screen.front_label.text == "P3(live)" and screen.history_cursor == screen.session_history.size(),
"stepping past the last history entry should return to the live card")
_assert(screen.reveal_button.visible and not screen.grade_row.visible,
"returning to a live, unrevealed card should restore the reveal prompt")
_assert(screen.tap_hint_label.visible and not screen.grade_row.visible,
"returning to a live, unrevealed card should restore the tap-to-reveal hint")
screen._go_next()
_assert(screen.session_history.size() == 3 and screen.session_history[2]["action"] == "skipped" and screen.session_history[2]["card_id"] == "card-3",
+19 -2
View File
@@ -29,7 +29,7 @@ func _initialize() -> void:
]
screen._render_card()
_assert(screen.front_label.text == "2 + 2 = ?", "the front should show only the prompt before reveal")
_assert(screen.reveal_button.visible, "the reveal button should be visible before the answer is shown")
_assert(screen.tap_hint_label.visible, "the tap-to-reveal hint should be visible before the answer is shown")
_assert(not screen.grade_row.visible, "grading buttons should be hidden before reveal")
_assert(not screen.back_label.visible, "the back should stay hidden before reveal")
@@ -38,7 +38,7 @@ func _initialize() -> void:
_assert(screen.extra_label.text == "Basic arithmetic" and screen.extra_label.visible, "the extra takeaway should render in its own labeled section")
_assert(screen.answer_caption.visible and screen.extra_caption.visible, "revealing a card with an extra should show both the answer and takeaway captions")
_assert(screen.grade_row.visible, "grading buttons should appear after reveal")
_assert(not screen.reveal_button.visible, "the reveal button should hide once the card is revealed")
_assert(not screen.tap_hint_label.visible, "the tap-to-reveal hint should hide once the card is revealed")
_assert(screen.grade_buttons["good"].text.contains("1d"), "each grade button should show its computed next interval")
# Cloze card: only the card's own variant is hidden on the front; back reveals everything.
@@ -60,6 +60,23 @@ func _initialize() -> void:
screen._reveal()
_assert(not screen.extra_label.visible and not screen.extra_caption.visible, "a card with no extra takeaway should hide that section instead of showing an empty caption")
# Tapping the card panel reveals the answer, same as pressing Space.
screen.current_note = {"type": "basic", "prompt": "Tap test", "answer": "Tapped", "extra": ""}
screen.current_card = {"id": "card-4", "variant": 0, "revision": 1}
screen.revealed = false
screen._render_card()
var click := InputEventMouseButton.new()
click.button_index = MOUSE_BUTTON_LEFT
click.pressed = true
screen._on_card_panel_input(click)
_assert(screen.revealed and screen.back_label.text == "Tapped", "clicking the card panel should reveal the answer")
# A second tap after reveal (or while browsing history) is a no-op - it must not re-trigger
# any reveal side effects (grading is guarded elsewhere; this only checks idempotence).
screen.back_label.text = "Tapped"
screen._on_card_panel_input(click)
_assert(screen.back_label.text == "Tapped", "tapping an already-revealed card should be a no-op")
screen.free()
quit(1 if failures > 0 else 0)