diff --git a/scenes/ccs_cases_2/doctor_office.tscn b/scenes/ccs_cases_2/doctor_office.tscn index ae8137e..44d5ad3 100644 --- a/scenes/ccs_cases_2/doctor_office.tscn +++ b/scenes/ccs_cases_2/doctor_office.tscn @@ -154,10 +154,10 @@ anchor_left = 1.0 anchor_top = 1.0 anchor_right = 1.0 anchor_bottom = 1.0 -offset_left = -255.0 -offset_top = -54.0 -offset_right = -52.0 -offset_bottom = -25.0 +offset_left = -330.0 +offset_top = -42.0 +offset_right = -72.0 +offset_bottom = -16.0 grow_horizontal = 0 grow_vertical = 0 mouse_filter = 2 diff --git a/scripts/ui/ccs_cases_2/doctor_office.gd b/scripts/ui/ccs_cases_2/doctor_office.gd index a185e11..9bc941b 100644 --- a/scripts/ui/ccs_cases_2/doctor_office.gd +++ b/scripts/ui/ccs_cases_2/doctor_office.gd @@ -12,7 +12,7 @@ const CHOICE_CROP := Rect2(80.0, 200.0, 1610.0, 485.0) const CHOICES_PER_PAGE := 4 const DIALOGUE_PAGE_MIN_CHARS := 32 const DIALOGUE_PAGE_MAX_CHARS := 82 -const DIALOGUE_BUBBLE_MIN_HEIGHT := 150.0 +const DIALOGUE_BUBBLE_MIN_HEIGHT := 110.0 const DIALOGUE_BUBBLE_MAX_HEIGHT := 240.0 const DIALOGUE_BUBBLE_VERTICAL_CHROME := 83.0 @@ -120,7 +120,8 @@ func _show_dialogue_page() -> void: dialogue_text.text = dialogue_pages[dialogue_page_index] dialogue_text.visible_ratio = 0.0 dialogue_reveal_complete = false - dialogue_continue_hint.visible = false + dialogue_continue_hint.text = "Click to continue ▼" + dialogue_continue_hint.visible = true dialogue_bubble.visible = true dialogue_bubble.grab_focus() dialogue_layout_generation += 1 @@ -190,11 +191,7 @@ func complete_dialogue_reveal() -> void: func _on_dialogue_reveal_finished() -> void: dialogue_tween = null dialogue_reveal_complete = true - dialogue_continue_hint.text = ( - "Click to see questions ▼" - if dialogue_page_index >= dialogue_pages.size() - 1 - else "Click to continue ▼" - ) + dialogue_continue_hint.text = "Click to continue ▼" dialogue_continue_hint.visible = true @@ -232,6 +229,24 @@ func _on_dialogue_bubble_input(event: InputEvent) -> void: accept_event() +func _unhandled_input(event: InputEvent) -> void: + if not dialogue_bubble.visible: + return + var should_advance := false + if event is InputEventMouseButton: + should_advance = event.button_index == MOUSE_BUTTON_LEFT and event.pressed \ + and get_global_rect().has_point(event.position) + elif event is InputEventScreenTouch: + should_advance = event.pressed and get_global_rect().has_point(event.position) + elif event is InputEventKey: + should_advance = event.pressed and not event.echo \ + and event.keycode in [KEY_ENTER, KEY_KP_ENTER, KEY_SPACE] + if not should_advance: + return + advance_dialogue() + get_viewport().set_input_as_handled() + + func _split_dialogue_pages(text: String) -> Array[String]: var normalized := text.strip_edges() if normalized.is_empty(): diff --git a/tests/ccs_cases_2_ui_test.gd b/tests/ccs_cases_2_ui_test.gd index 3633a6f..0dd5ecc 100644 --- a/tests/ccs_cases_2_ui_test.gd +++ b/tests/ccs_cases_2_ui_test.gd @@ -92,21 +92,29 @@ func _run() -> void: _assert(_label_content_fits(screen.active_scene.dialogue_text), "the first patient response page should fit inside its speech bubble") _assert(is_equal_approx(screen.active_scene.dialogue_text.offset_left, 96.0), "patient dialogue should retain the increased left inset") _assert(_bubble_matches_content(screen.active_scene), "the speech bubble should size itself to the wrapped response page") - screen.active_scene.advance_dialogue() + _assert(screen.active_scene.dialogue_continue_hint.visible, "the continuation prompt should remain visible while text is typing") + _assert(screen.active_scene.dialogue_continue_hint.text == "Click to continue ▼", "every dialogue page should use consistent continuation wording") + var outside_click := _left_click(screen.active_scene.get_global_rect().end + Vector2(50.0, 50.0)) + screen.active_scene._unhandled_input(outside_click) + _assert(not screen.active_scene.dialogue_reveal_complete, "clicks outside the doctor scene should not affect dialogue") + var scene_click := _left_click(screen.active_scene.get_global_rect().get_center()) + screen.active_scene._unhandled_input(scene_click) _assert(screen.active_scene.dialogue_page_index == 0, "clicking during typing should complete rather than skip the current page") _assert(screen.active_scene.dialogue_continue_hint.visible, "completed dialogue pages should invite the learner to continue") await create_timer(1.35).timeout _assert(screen.active_scene.is_patient_dialogue_visible(), "patient dialogue should remain until the learner advances it") _assert(not screen.active_scene.choice_container.visible, "questions must not appear on a timer") while screen.active_scene.dialogue_page_index < screen.active_scene.dialogue_pages.size() - 1: - screen.active_scene.advance_dialogue() + screen.active_scene._unhandled_input(scene_click) await process_frame _assert(_label_content_fits(screen.active_scene.dialogue_text), "every patient response page should fit inside its speech bubble") screen.active_scene.complete_dialogue_reveal() - screen.active_scene.advance_dialogue() + screen.active_scene._unhandled_input(scene_click) await process_frame _assert(not screen.active_scene.is_patient_dialogue_visible(), "questions should replace patient dialogue after the final learner click") _assert(screen.active_scene.choice_container.get_child_count() == 4, "the first question page should contain four choices") + screen.active_scene._unhandled_input(scene_click) + _assert(screen.active_scene.choice_container.get_child_count() == 4, "scene clicks should not affect explicit question controls") _assert(screen.active_scene.choice_pager.visible, "eight choices should display paging controls") _assert(screen.active_scene.choice_page == 0, "new question sets should begin on the first page") _assert(_visible_choice_ids(screen.active_scene) == ["topic_0", "topic_1", "topic_2", "topic_3"], "the first page should preserve choice order and IDs") @@ -152,6 +160,7 @@ func _run() -> void: await process_frame var short_height: float = sizing_scene.dialogue_bubble.size.y _assert(is_equal_approx(short_height, sizing_scene.DIALOGUE_BUBBLE_MIN_HEIGHT), "short dialogue should use the minimum bubble height") + _assert(sizing_scene.dialogue_bubble.get_global_rect().encloses(sizing_scene.dialogue_continue_hint.get_global_rect()), "the continuation hint should fit inside a minimum-height bubble") sizing_scene.show_patient_dialogue("Line one explains the symptom.\nLine two adds timing.\nLine three adds context.\nLine four concludes.") await process_frame _assert(sizing_scene.dialogue_bubble.size.y > short_height, "multi-line dialogue should increase the bubble height") @@ -271,6 +280,14 @@ func _bubble_matches_content(scene: Control) -> bool: and _label_content_fits(scene.dialogue_text) +func _left_click(position: Vector2) -> InputEventMouseButton: + var event := InputEventMouseButton.new() + event.button_index = MOUSE_BUTTON_LEFT + event.pressed = true + event.position = position + return event + + func _assert(condition: bool, message: String) -> void: if condition: return